Context在Android开发中无处不在,很多时候我们都是随便拿来用,只要不报错就行,但是当我们考虑到APP的性能时,就要求我们必须正确使用各种Context。
1.Context的作用
首先,我们知道,Context可以用来实现很多功能的调用,比如:
//获取资源管理器对象,进而可以访问到例如string,color等资源
Resources res = context.getResources();
//启动指定Activity
context.startActivity(new Intent(this,MainActivity.class));
//获取各种系统服务
TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHOMY_SERVICE);
//获取系统文件目录
File internalDir = context.getCacheDir();
由此可见,正确使用Context非常重要,在具体问题上需要区别对待,否则很可能会引入各种问题。接下来,我们来看看Context具体有哪些种类。
2.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和调用者处于相同的应用进程中,那么getContext()将反馈应用全局唯一的Context实例。如果是其他进程调用的ContentProvider,那么ContentProvider将持有自身所在进程的Context实例。
3.错误使用Context导致内存泄露
错误地使用Context可能会导致内存泄露,典型的例子是在实现单例模式时使用Context,如下所示:
public class SingleInstance{
private Context context;
private static SingleInstance instance;
private SingleInstance(Context context){
context = context;
}
public static SingleInstance getInstance(Context context){
if(instance == null){
instance = new SingleInstance(context);
}
return instance;
}
}
如果使用者调用getInstance时传入的Context是一个Activity或者Service的实例,那么在应用退出前,由于单例一直存在,会导致对应的Activity或者Service被单例引用,从而不会被垃圾回收,Activity或者Service中关联的其他View或者数据结构对象也不会被释放,从而导致内存泄露。正确的做法是使用Application Context,因为它是应用唯一的,而且生命周期是跟应用一样的,正确的单例实现如下:
public class SingleInstance{
private Context context;
private static SingleInstance instance;
private static SingleInstance getInstance(Context context){
if(instance==null){
instance = new SingleInstance(context.getApplicationContext());
}
return instance;
}
}
3.不同Context的对比
不同组件中的Context能提供的功能不尽相同,我这里将它总结在下表中。
其中NO1表示对应的组件并不是真的不可以启动Activity,而是建议不要这么做,因为这些组件会在新的Task中创建Activity,而不是在原来的Task中。
NO2也表示不建议这么做,因为在非Activity中进行Layout Inflation,会使用系统默认的主题,而不是应用中设置的主题。
NO3表示在Android4.2及以上的系统上,如果注册的BroadcastReceiver是null时是可以的,用来获取sticky广播的当前值。