引言
Context应该是每个Android入门开发者第一个接触到的概念,它代表当前上下文环境,可以用来实现很多功能的调用。
我们最常见的有这些
//获取资源管理器对象,进而可以访问到string,color等资源
Resources resources = context.getResources();
//启动指定的Activity
context.startActivity(new Intent(this,MainActivity.class));
//获取各种系统服务
TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
//获取系统文件目录
File internalDir = context.getCacheDir();
File internalDir = context.getExternalCacheDir();
可见,正确理解context很重要。在实际开发中我们会遇到各种各样的context,但是你要知道,每一个context都是有自己的含义的,并不是都相同的哦。这点你搞错了,那么开发也就进行不下去了。
Context种类
我们一般按照Context依托的组件和用途不同来进行分类:
- Application:Android应用中的默认单例类,在Activity或者service中可以通过getApplication()获取到这个单例。通过context.getApplicationContext()可以获取到应用全局唯一Context实例。
- Activity/Service :这两个类都是ContextWrapper的子类,在这两个类中可以通过getBaseContext()获取到他们的Context实例,不同的Activity或者Service实例他们的Context都是相互独立的,不会复用。
- BroadcastReceiver:和Activity以及Service不同,BroadcastReceiver本身并不是Context的子类,而是在回调函数onReceive()中由Android框架传入一个Context的实例。系统传入的这个Context实例是被阉割过的,它并不能调用registerReceiver()已经bindService()这两个函数
- ContentProvider:同样的,ContentProvider也不是Context的子类。同样是在创建的时候系统给传一个Context实例,然后在ContentProvider中就可以通过getContext()函数来获取。如果调用者和ContentProvider处于一个相同的应用进程也就是说在同一个应用中调用ContentProvider的话,getContext()会返回应用的全局唯一Context实例。相反如果不在同一个进程,那么他们各自返回自己所在进程的Context实例。
错误使用将导致内存泄漏
错误的使用Context将导致内存泄漏,典型的例子是在实现单例模式的时候使用Context而出现的内存泄漏:
public class SingleInstance{
private Context mContext;
private static SingleInstance sInstance;
private SingleInstance(Context context){
mContext = context;
}
public static SingleInstance getInstance(Context context){
if(sInstance == null){
sInstance = new SingleInstance(context);
}
return sInstance;
}
}
对于这个实例,如果在创建这个单例的时候传入的context是Activity或者Service的,那么在整个应用结束之前,这个单例会一直存在,从而到这当初传给它Context的Activity或者Service也会一直存在不会被垃圾回收,这样的话Activity或者Service中关联的其它View或者数据等等也就不会被释放,最后导致了内存泄漏。在这个单例模式中传入的正确Context必须是Application Context,因为它是应用全局唯一的。而且生命周期跟应用的声明周期是同步的。
对于这种问题,我们也可以在创建单例的时候,从根源就把这个隐患给避免掉,具体做法如下:
public class SingleInstance{
private Context mContext;
private static SingleInstance sInstance;
private SingleInstance(Context context){
mContext = context;
}
public static SingleInstance getInstance(Context context){
if(sInstance == null){
sInstance = new SingleInstance(context.getApplicationContext());
}
return sInstance;
}
}
不同的Context的对比
不同的组件中的context功能是不一样的,总结起来如下:
功能 | Application | Activity | Service | BroadcastReceiver | ContentProvider |
---|---|---|---|---|---|
显示dialog | NO | YES | NO | NO | NO |
启动Activity | NO[1] | YES | NO[1] | NO[1] | NO[1] |
实现Layout inflation | NO[2] | YES | NO[2] | NO[2] | NO[2] |
启动Service | YES | YES | YES | YES | YES |
绑定Service | YES | YES | YES | YES | NO |
发送Broadcast | YES | YES | YES | YES | YES |
注册Broadcast | YES | YES | YES | YES | NO[3] |
加载资源 Resource | YES | YES | YES | YES | YES |
NO[1]:表示对应的组件并不是不可以,而是不建议。因为这些组件会在新的Task中创建Activity而不是在原来的Task中创建
NO[2]:也是表示不建议,因为在非Activity中进行Layout Inflation,会使用系统默认的主题,而不是应用中设置的主题
NO[3]:表示在Android4.2以及以上的系统中,如果注册BroadcastReceiver是null时也是可以的,用来获取sticky广播的当前值