Android_Context详解
Context对象是如此常见和传递使用,它可能会很容易产生并不是你预期的情形。加载资源、启动一个新的Activity、获取系统服务、获取内部文件路径以及创建view(其实还远不止这些)统统都需要Context对象来完成。我(原文作者)想做的只是给大家提供一些Context是如何工作的见解,以及让大家在应用中更有效的使用Context的技巧。
本文引用地址:https://www.eepw.com.cn/article/201609/304658.htmContext的类型
并不是所有的context实例都是等价的。根据Android应用的组件不同,你访问的context推向有些细微的差别。
Application - 是一个运行在你的应用进程中的单例。在Activity或者Service中,它可以通过getApplication()函数获得,或者人和继承于context的对象中,通过getApplicationContext()方法获得。不管你是通过何种方法在哪里获得的,在一个进程内,你总是获得到同一个实例。
Activity/Service - 继承于ContextWrapper,它实现了与context同样API,但是代理这些方法调用到内部隐藏的Context实例,即我们所知道的基础context。任何时候当系统创建一个新的Activity或者Service实例的时候,它也创建一个新的ContextImpl实例来做所有的繁重的工作。每一个Activity和Service以及其对应的基础context,对每个实例来说都是唯一的。
BroadcastReciver - 它本身不是context,也没有context在它里面,但是每当一个新的广播到达的时候,框架都传递一个context对象到onReceive()。这个context是一个ReceiverRestrictedContext实例,它有两个主要函数被禁掉:registerReceiver()和bindService()。这两个函数在BroadcastReceiver.onReceive()不允许调用。每次Receiver处理一个广播,传递进来的context都是一个新的实例。
ContentProvider - 它本身也不是一个Context,但是它可以通过getContext()函数给你一个Context对象。如果ContentProvider是在调用者的的本地(例如,在同一个应用进程),getContext()将返回的是Application单例。然而,如果调用这个ContentProvider在不同的进程的时候,它将返回一个新创建的实例代表这个Provider所运行的包。
保存引用
第一个我们需要解决问题是,在一个对象或者类内部保存一个context引用,而它生命周期却超过其保存引用的对象的生命周期。例如,创建一个自定义的单例,它需要一个context来加载资源或者获取ContentProvider,从而保存一个指向当前Activiy或者Service的引用在单例中。
糟糕的单例
[java]
public class CustomManager {
private static CustomManager sInstance;
public static CustomManager getInstance(Context context) {
if (sInstance == null) {
sInstance = new CustomManager(context);
}
return sInstance;
}
private Context mContext;
private CustomManager(Context context) {
mContext = context;
}
}
这里的问题在于,我们不知道这个context是从哪里来的,并且如果保存一个最终指向的是Activity或者Servece的引用是并不安全的。这是一个问题,是因为一个单例在类的内部维持一个唯一的静态引用,这意味着我们的对象,以及所有其他它所引用的对象,将永远不能被垃圾回收。假如这个Context是一个Activity,我们将保存与这个Activity相关的所有的view以及其他大的对象,从而造成内存泄漏。
为了解决这个问题,我们修改单例永远只是保存Application context:
改善的单例:
[java
public class CustomManager {
private static CustomManager sInstance;
public static CustomManager getInstance(Context context) {
if (sInstance == null) {
//Always pass in the Application Context
sInstance = new CustomManager(context.getApplicationContext());
}
return sInstance;
}
private Context mContext;
private CustomManager(Context context) {
mContext = context;
}
}
现在这个例子中,我们的Context来自哪里都没有关系,因为我们这里保存引用是安全的。Application Context 本身就是一个单例,所以我们再创建另外一个static引用,不会造成任何内存泄漏。另外一个很好的例子是,在后台线程或者一个等待的Handler中保存Context的引用,也可以使用这样的方法。
为什么我们不能总是引用Application context呢?正如前面说的,引用Application context永远不用担心内存泄漏的问题。问题的答案,就像我在开始的介绍中说的,是因为不同context并不是等价的。
Context的能力
Conext能做的通用操作决定于这个context最初来源于哪里。下表所列的是,在应用中常见的会收到context对象的,以及对应的每种情况,它可以用于哪些地方:
Application | Activity | Service | ContentProvider | BroadcastReceiver | |
---|---|---|---|---|---|
Show a Dialog | NO | YES | NO | NO | NO |
Start an Activity | NO1 | YES | NO1 | NO1 | NO1 |
Layout Inflation | NO2 | YES | NO2 | NO2 | NO2 |
Start a Service | YES | YES | YES | YES | YES |
Bind to a Service | YES | YES | YES | YES | NO |
Send a Broadcast | YES | YES | YES | YES | YES |
Register BroadcastReceiver | YES | YES | YES | YES | NO3 |
Load Resource Values | YES | YES | YES | YES | YES |
评论