一、概念
Context是运行上下文环境,具有以下功能:
获取应用资源,如drawable、string、assets;
获取文件目录,如获取/data/分区的数据目录、获取sdcard目录;
获取系统服务,如AMS、WMS、PMS;
操作四大组件,如启动界面、发送广播、绑定服务、打开数据库;
检查授予权限,如应用向外提供服务时,可以判定申请者是否具备访问权限。
二、类图
Context类是一个抽象类,具体实现在ContextImpl类中。
ContextWrapper是Context的一个包装类,其里面所有的方法实现都是调用其内部mBase变量的方法,而mBase就是ContextImpl对象。ContextWrapper有一个ContextThemeWrapper子类,该类扩展了主题相关的方法。
Application和Service继承自ContextWrapper,而Activity继承自ContextThemeWrapper,这是因为Activity在启动的时候系统都会加载一个主题,也就是我们平时在AndroidManifest.xml文件里面写的android:theme=”@style/AppTheme”属性,而Applicaton和Service都和UI界面没有关系,因此它们继承自ContextWrapper。
Application扩展了应用程序的生命周期,Activity扩展了界面显示的生命周期,Service扩展了后台服务的生命周期,它们在父类Context的基础上进行了不同维度的扩展。虽然BroadcastReceiver和ContentProvider不是Context的子类,但是BroadcastReceiver的onReceive()方法和ContentProvider的构造方法都需要把Context作为参数传入,虽然它们不继承于Context,但是它们都依赖于Context,换个角度看它们也是装饰器,包装了Context。
三、Context类型
Application Context
Application Context在应用进程里是一个单例,可以在Activity或Service中通过getApplication()来访问,也可以在任意继承Context的对象中通过getApplicationContext()来访问。不管以何种形式访问, 在同一应用进程中获得的Application Context实例都是同一个。
Activity/Service Context
Activity/Service继承自ContextThemeWrapper/ContextWrapper,ContextWrapper作为Context(一个Base Context)的装饰器,实现了和Context一样的接口。当创建一个Activity/Service实例时,也会创建一个ContextImpl的实例来真正处理Context接口所描述的工作,每个Activity/Service实例都持有一个对应的Base Context的实例。
BroadcastReceiver中的Context
BroadcastReceiver不是一个Context,但广播事件到来时会传递一个Context给onReceive()方法。
当广播接收器在manifest中静态注册时,传递的是ReceiverRestrictedContext实例,该实例全局只有一个,它禁用了Context的registerReceiver()方法和bindService()方法。
当广播接收器在activity/service中动态注册时,传递的是activity/service实例(向上转型为Context类型),没有禁用Context的方法。
ContentProvider中的Context
ContentProvider不是一个Context,但是可以通过getContext()方法来获取一个Context实例,该实例属于Application级别。
四、应用场景
数字1:启动Activity在这些类中是可以的,但是需要创建一个新的task。
数字2:在这些类中去layout inflate是合法的,但是会使用系统默认的主题样式,如果自定义了某些样式可能不会被使用。
数字3:在receiver为null时允许,在4.2或以上的版本中,用于获取黏性广播的当前值。(可以无视)
ContentProvider、BroadcastReceiver之所以在上述表格中,是因为在其内部方法中都有一个context用于使用。
对于应用场景,实际上只要把握住一点:凡是跟UI相关的,都应该使用Activity做为Context来处理;其他的一些操作,使用Activity、Service、Application等实例都可以,但是要特别注意Context引用的持有,防止内存泄漏。
五、注意事项
不同类型Context的区别
// 在Activity.onCreate()中插入以下代码:
Log.i(TAG, "Application: " + getApplication());
Log.i(TAG, "Application Context: " + getApplicationContext());
Log.i(TAG, "Activity: " + this);
Log.i(TAG, "Activity Context:" + this);
Log.i(TAG, "Application BaseContext: " + getApplication().getBaseContext());
Log.i(TAG, "Activity BaseContext: " + getBaseContext());
// 得到的运行结果:
I MainActivity: Application: com.duanqz.github.DemoApp@cf8644e
I MainActivity: ApplicationContext: com.duanqz.github.DemoApp@cf8644e
I MainActivity: Activity: com.duanqz.github.MainActivity@bbcadec
I MainActivity: Activity Context: com.duanqz.github.MainActivity@bbcadec
I MainActivity: Application BaseContext: android.app.ContextImpl@6a6a96f
I MainActivity: Activity BaseContext: android.app.ContextImpl@770267
- getApplication()和getApplicationContext()返回的是同一个对象,但对象的类型不同,前者是Application,后者是Context。Java是强类型的语言,从Application到Context相当于向上转型,会丢失掉一些接口的访问入口。
- Activity和Activity Context也是同一个对象,不同的类型。
- Application和Activity的Base Context都是ContextImpl对象,正是这个Context真正的实现类,被外围的修饰器包装了一下,才形成不同功能的类。
Context导致的内存泄露问题
public class MainActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
... // Other codes
Singleton.get(this);
}
}
public class Singleton {
private static Singleton sMe;
private Singleton(Context context) {
// Do something with context
}
public static synchronized Singleton get(Context context) {
if (sMe == null) {
sMe = new Singleton(context);
}
return sMe;
}
}
//避免内存泄露方法:
public class MainActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
... // Other codes
Singleton.get(getApplicationContext());
}
}
单例的实现都包含一个静态变量,在Java的垃圾回收机制中,静态变量是GC ROOT,某对象只要存在到达GC ROOT的路径,就不会被回收。如果对象的生命周期已经结束但是不能被回收,则会出现内存泄露问题。
使用Application Context的场景:
Context的生命周期超出Activity或Service的生命周期时,如工具类。
使用Activity Context的场景:
Context的生命周期小于Activity时,如初始化Activity的子控件、弹出对话框。