1. Context的代码层级
2. Context 作用以及作用域
public abstract class Context {
public abstract AssetManager getAssets();
public abstract Resources getResources();
public abstract PackageManager getPackageManager();
public abstract ContentResolver getContentResolver();
public abstract Looper getMainLooper();
public Executor getMainExecutor() {
return new HandlerExecutor(new Handler(getMainLooper()));
}
public abstract Context getApplicationContext();
//...
}
Context类本身是一个纯abstract类,它有两个具体的实现子类:ContextImpl和 ContextWrapper。
ContextWrapper类是一个包装而已,ContextWrapper构造函数中必须包含一个真正的Context引用,同时 ContextWrapper中提供了attachBaseContext()用于给ContextWrapper对象中指 定真正的Context对象,调用ContextWrapper的方法都会被转向其所包含的真正的 Context对象。
ContextThemeWrapper类,如其名所言,其内部包含了与主题 (Theme)相关的接口,这里所说的主题就是指在AndroidManifest.xml中通过 android:theme为Application元素或者Activity元素指定的主题。当然,只有Activity 才需要主题,Service是不需要主题的,因为Service是没有界面的后台场景,所以 Service直接继承于ContextWrapper,Application同理。而ContextImpl类则真正实 现了Context中的所以函数,应用程序中所调用的各种Context类的方法,其实现均 来自于该类。
Context的两个子类分工明确,其中ContextImpl是Context的具体实现类,ContextWrapper是Context的包装类。Activity, Application,Service虽都继承自ContextWrapper(Activity继承自ContextWrapper 的子类ContextThemeWrapper),但它们初始化的过程中都会创建ContextImpl对象,由ContextImpl实现Context中的方法。
2.1 作用
- 四大组件的交互,包括启动 Activity、Broadcast、Service,获取 ContentResolver 等。
- 获取系统 / 应用资源,包括 AssetManager、PackageManager、Resources、SystemService 以及 color、string、drawable 等。
- 文件、SharedPreference、数据库相关。
- 其他辅助功能,比如设置 ComponentCallbacks,即监听配置信息改变、内存不足等事件的发生。
2.2 作用域
虽然 Context 神通广大,但是并不是随便拿到一个 Context 实例就可以为所欲为,还是有一些限制的。在绝大多数场景下,Activity、Service 和 Application 这三种类型的 Context 都是通用的,不过也有几种场景比较特殊,比如启动 Activity、弹出 Dialog。Android 是不允许 Activity 或 Dialog 凭空出现的,一个 Activity 的启动必须建立在另外一个 Activity 的基础之上,也就以此形成任务栈。而 Dialog 则必须在一个 Activity 的上面弹出(除非是 System Alert 类型的 Dialog),因此在这种场景下,我们只能使用 Activity 类的 Context。
所以,最常见的错误场景就是:使用 ApplicationContext 去启动一个 LaunchMode 为 standard 的 Activity
会报错,因为非 Activity 类型的 Context 没呀所谓的任务栈。
Context作用域 | Application | Activity | Service |
---|---|---|---|
Show a Dialog | No | YES | NO |
Start an Activity | 不推荐 | YES | 不推荐 |
Layout Inflation | 不推荐 | YES | 不推荐 |
Start a Service | YES | YES | YES |
Send a Broadcast | YES | YES | YES |
Register Broadcast Receiver | YES | YES | YES |
Load Resoure Values | YES | YES | YES |
凡是跟UI相关的,都应该使用Activity做为Context来处理;其他的一 些操作,Service,Activity,Application等实例都可以,当然了,注意Context引用的持有,防止内存泄漏。
一个应用程序有几个Context?
实这个问题本身并没有什么意义,关键还是在于对Context的理解,从上面的关系图我们已经可以得出答案了,在应用程序中Context的具体实现子类就是: Activity,Service,Application。那么 Context数量=Activity数量+Service数量 +1 。当然如果你足够细心,可能会有疑问:我们常说四大组件,这里怎么只有 Activity,Service持有Context,那Broadcast Receiver,Content Provider呢? Broadcast Receiver,Content Provider并不是Context的子类,他们所持有的 Context都是其他地方传过去的,所以并不计入Context总数。上面的关系图也从 另外一个侧面告诉我们Context类在整个Android系统中的地位是多么的崇高,因为很显然Activity,Service,Application都是其子类,其地位和作用不言而喻。
Context能干什么?
Context到底可以实现哪些功能呢?这个就实在是太多了,弹出Toast、启动 Activity、启动Service、发送广播、操作数据库等等都需要用到Context。
如何获取Context?
通常有四种方式获取:
- View.getContext(),放回当前View对象的Context对象,通常是当前正在展开的Activity对象。
- Activity.getApplicationContext(),获取当前Activity所在应用进程Context对象,通常我们使用Context对象时,要优先考虑这个全局的进程Content。
- ContextWrapper.getBaseContext(),用来获取一个ContextWrapper进行装饰前的Context,可以使用这个方法,这个方法在实际开发中使用并不多,也不建议使用。
Activity.this,返回当前Activity的实例,如果是UI控件需要使用Activity作为Context对象,但是默认的Toast实际上使用ApplicationContext也可以。
getApplication()和getApplicationContext()
上面说道获取低昂钱Application对象用getApplicationContext,它与getApplication有什么区别?
- Application本身就是一个Context,所以获取getApplicationContext得到的结果就是Application本身的实例。
- 但是他们在作用域上有较大区别。getApplication只有在Activity和Service中才能调用的到,对于 BroadcastReceiver 和 ContentProvider 只能使用 getApplicationContext。
- 但是对于 ContextProvider 使用 getApplicationContext 可能会出现空指针问题。当同一个进程有多个 apk 的情况下,对于第二个 apk 是由 provide 方式拉起的,provide 创建过程并不会初始化所在 application,此时返回的结果是 null。
public class MyReceiver extends BroadcastReceiver{
@override
public void onReceive(Context cotext,Intent intent){
Application app = (Application)context.getApplicationContext();
}
}
Context引起的内存泄漏
Context并不能随意乱用,用的不好有可能会引起内存泄漏的问题,下面给出两种错误的引用方式。
错误的单例模式
public class Singleton{
private static Singleton instance;
private Context mContext;
private Singleton(Context context){
this.context = context;
}
public static Singleton getInstance(Context context){
if(instance == null){
instance = new Singleton(context);
}
return instance;
}
}
这是一个非线程安全的单例模式,instance作为静态对象,其生命周期要长于普通的对象,其中也包含Activity,加入Activity A去getInstance对象,传入this,常驻内存的Singleton保存了他传人的Activity A对象,并一直持有,即使Activity被销毁掉,但因为它的引用还存在于一个Singleton中,就不可能被GC掉,这样就会导致内存泄漏。
View持有Activity引用
public class MainActivity extends Activity{
private static Drawable mDrawable;
protected void onCreate(Bundle saveInstanceState){
super.onCreate(saveInstanceSatet);
setContentView(R.layout.activity_main);
ImageView iv = new ImageView(this);
mDrawable = getResources().getDrawable(R.drawable.ic_launcher);
iv.setImageDrawable(mDrawable);
}
}
有一个静态的Drawable对象,当ImageView设置这个Drawable时,ImageView保存了mDrawable的引用,而ImageView传入this是MainActiviy的mContext,因为被static修饰的mDrawable是常驻内存的,MainActivity是它的间接引用,MainActivity被注销时,也不能被GC掉,所以造成内存泄漏。
正确使用Context
一般Context造成的内存泄漏,几乎都是当Context销毁的时候,缺因为被引用导致销毁失败,而Application的Context对象可以理解为随着进程存在的,所以我们总结出使用Context的正确姿势:
- 当Application的Context能搞定的情况下,并且生命周期长的对象,优先使用Application的Context。
- 不要让生命周期长于Activity的对象持有Activity的引用。
- 尽量不要在Activity中使用非静态内部类,因为非静态内部类会隐式持有外部类示例的引用,如果使用静态内部类,将外部示例引用为弱引用持有。