Context
前言
说到Context,它其实是我们熟悉又陌生的小伙伴了,一开始我们都感知不到Context的存在,而是被迫使用Context的,比如自定义View构造函数就要传入Context,发送广播的时候,又必须使用Context,于是乎,我们通常会设计一个成员mContext来保存外部传入的Context,但是这样有造成一些问题:
- 好不容易想实现控件的代码解耦,为什么要把Context传来传去呢?
- 为什么不能像在Activity中一样,直接调用sendBroadcast()就可以了呢?
- 为什么通过Context可以调用很多Android的接口,譬如getString(), getColor(), startActivity()呢?
下面我们就来解决这些问题~
Context的设计思想
面向应用程序的设计
Android有意淡化进程的概念,在开发一个Android应用程序的时候,通常都不需要关心目标对象运行在哪个进程,只需要表面意图(Intent),比如打电话,查看图片,打开链接也不需要关心系统接口是在哪个进程实现的,只需要通过Context发起调用。对于一个Android应用,Context就像运行环境一样,一旦持有Context就会成为一个Android的公民,从而享有Android提供的一些服务
- 获取应用资源,譬如:drawable、string、assets
- 操作四大组件,譬如:启动界面、发送广播、绑定服务、打开数据库
- 操作文件目录,譬如:获取/data/分区的数据目录、获取sdcard目录
- 检查授予权限,譬如:应用向外提供服务时,可以判定申请者是否具备访问权限
- 获取其他服务,有一些服务有专门的提供者,譬如:包管理服务、Activity管理服务、窗口管理服务
Context类族的设计
Context族的设计用到了装饰者模式,基于Context制定的接口,ContextImp负责实现接口的具体功能,对外提供使用时,ContextImpl需要被包装(Wrapper)一下,这就有了ContextWrapper这个装饰者,ContextWrapper的设计目的在于扩展功能,虽然Context已经提供了十分丰富的功能,但仍不能满足最终应用程序编程的需要,因此Android又扩展了一些修饰器,包括Application、Activity和Service
Application扩展了应用程序的生命周期,Activity扩展了界面显示的生命周期,Service扩展了后台服务的生命周期,它们在父类Context的基础上进行了不同维度的扩展,同时也仍可以将它们作为Context使用,这可以解释很多Applicaiton、Activity和Service的使用方式,但很多问题也随之而来
- 为什么四大组件的另外两个BroadcastReceiver和ContentProvider不是Context的子类呢?
- 为什么Applictaion、Activity和Service不直接继承ContextImp呢?
下面以ContextProvider的构造函数和BrocadcastReceiver.onReceive()方法为例
// ContentProvider
public ContentProvider(
Context context,
String readPermission,
String writePermission,
PathPermission[] pathPermissions) {
mContext = context;
mReadPermission = readPermission;
mWritePermission = writePermission;
mPathPermissions = pathPermissions;
}
// BroadcastReceiver
public abstract void onReceive(Context context, Intent intent);
ContentProvider和BroadcastReceiver都需要把Context作为参数传入,虽然它们不继承于Context,但它们都依赖于Context,换个角度看:它们就是修饰器,包装了Context。因为这两个组件在使用上与Activity和Service存在较大的区别,所以它们的实现方式存在较大差异
往深一步理解,装饰者模式的优势也体现出来了,比如Application、Activity和Service都可以作为BroacastReceiver的载体,只需要通过它们各自的Context去注册广播接收器就可以了,将BroadcastReceiver修饰在它们之,就可以形成新的功能扩展,而不是去实现一些可以接收广播的Applicaiton、Activity或Service类
小结
至此,Context的设计思想已经渗透了,从应用程序与系统通信的角度来说,它是应用进程与系统对话的一个接口;从使用的角度,更是可以将Context理解为应用进程的Android运行环境,想要什么资源,都可以向Context索取;而从实现的角度来说Context的类族运用了装饰者模式,Android最核心的四大组件都可以理解成装饰者,它们从不同的维度对Context进行了扩展
Context源码解析
上面也提及到了Application、Activity、Service都是ContextImp的装饰类,在这个源码解析的过程,笔者将忽略一些实现的细节,重点解决的是两个问题:
- ComtextImp是如何被创建的?
- ComtextImp是如何被装饰,然后成为我们耳熟能详的Activity、Service、Application的?
ContextImp是如何被创建的?
解决这个问题,首先明确一点,ContextImp有多少种?
- SystemContext:系统进程SystemServer的Context
- AppContext:应用进程的Context
- ActivityContext:Activity的Context,只有ActivityContext跟界面显示相关,需要传入activityToken和有效的DisplayId
SystemContext的创建
public final class SystemServer {
private void run() {
...
createSystemContext();
startBootstrapServices(); //开始启动服务
...
}
}
private void createSystemContext() {
ActivityThread activityThread = ActivityThread.systemMain();
mSystemContext = activityThread.getSystemContext();
mSystemContext.setTheme(android.R.style.Theme_DeviceDefault_Light_DarkActionBar);
}
以上是SystemContext的创建过程,这里就不去深究了,重点看一下的是后面两种Context:
AppContext的创建
// ContextImpl.createAppContext()
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
return new ContextImpl(null, mainThread,
packageInfo, null, null, 0, null, null, Display.INVALID_DISPLAY);
}
// ContextImpl.constructor()
private ContextImpl(ContextImpl container, ActivityThread mainThread,
LoadedApk packageInfo, IBinder activityToken, UserHandle user, int flags,
Display display, Configuration overrideConfiguration, int createDisplayWithId) {
mOuterContext = this; // 外围包装器,暂时用
...
mMainThread = mainThread; // 主线程
mActivityToken = activityToken; // 关联到系统进程的ActivityRecord
mFlags = flags;
...
mPackageInfo = packageInfo; // LoadedApk对象
mResourcesManager = ResourcesManager.getInstance();
...
Resources resources = packageInfo.getResources(mainThread);
...
mResources = resources; // 通过各种计算得到的资源
...
mContentResolver = new ApplicationContentResolver(this, mainThread, user); // 访问ContentProvider的接口
}
上面这段代码片段是创建一个AppContext,主要展示的部分是初始化一些关键,日常开发涉及比较多的一些成员,特别注意的是Context中会初始化一个mContentResolver对象,所以,可以通过Context操作数据库
ActivityContext的创建
static ContextImpl createActivityContext(ActivityThread mainThread,
LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,
Configuration overrideConfiguration) {
...
ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName,
activityToken, null, 0, classLoader);
displayId = (displayId != Display.INVALID_DISPLAY) ? displayId : Display.DEFAULT_DISPLAY;
... context.setResources(resourcesManager.createBaseActivityResources(activityToken,
packageInfo.getResDir(),
splitDirs,
packageInfo.getOverlayDirs(),
packageInfo.getApplicationInfo().sharedLibraryFiles,
displayId,
overrideConfiguration,
compatInfo,
classLoader));
context.mDisplay = resourcesManager.getAdjustedDisplay(displayId,
context.getResources());
return context;
}
从上面的片段源码可以看出,其实ActivityContext的创建和AppContext的创建区别不大,只是配置参数不一样而已
小结
至此,ContextImp创建的关键部分已经总结得差不多了,接下来我们就来解决第二个问题:ContextImp是如何被装饰的?
ContextImp是如何被装饰
ContextImp的装饰过程,我们列举两个例子来说明,分别是Applictaion和Activity
Applictaion部分
public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) {
...
if (forceDefaultAppClass || (appClass == null)) {
appClass = "android.app.Application";
}
try {
java.lang.ClassLoader cl = getClassLoader();
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
appContext.setOuterContext(app);
} catch (Exception e) {...}
...
}
static public Application newApplication(Class<?> clazz, Context context)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
Application app = (Application)clazz.newInstance();
app.attach(context);
return app;
}
// Application.attach()
final void attach(Context context) {
attachBaseContext(context);
mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}
// ContextWrapper.attachBaseContext()
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}
这是Application创建的片段代码,从上面的代码我们也可以看到,系统中其实是用反射机制去创建Application的,果应用程序没有继承实现Application,则默认使用android.app.Application
这个包名进行反射。构建Application对象完成后,就通过attach()方法去绑定一个Context,这里的mLoadedApk其实是apk在内存中的数据结构,这一绑定就相当于在ContextWrapper中关联了一个ContextImpl,于是,这样一层修饰包装的关系就套上了
Activity部分
if (activity != null) {
// 3. 创建Activity的Context
Context appContext = createBaseContextForActivity(r, activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
...
// 4. 将Context包装进Activity
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window);
...
上面的片段代码是Activity启动过程中的performLaunchActivity中的其中一个片段,我们可以看到,创建完成ActivityContext之后其实它的装饰方式和appliction是类似的,而这个createBaseContextForActivity方法最终只是调用了上面createActivityContext()而已,这里就不再重复了
小结
可见,Activity和Application的Context构建过程极为相似,这也是符合逻辑的,因为它们本质上就是Context,只不过功能维度不同。其实Service的Context构建过程也很相似。无非都是构建ContextImpl对象,然后在外面附上一层不同的装饰而已
Context的注意事项
从前文中可以看出,Android中有好几种不同的Context,在一个Activity中,就可以获取到以下几种Context:
- getApplication()
- getApplicationContext()
- getBaseContext()
- Activity.this
// 插入代码
Log.i(TAG, "Application: " + getApplication());
Log.i(TAG, "ApplicationContext: " + getApplicationContext());
Log.i(TAG, "Activity: " + this);
Log.i(TAG, "ActivityContext:" + this);
Log.i(TAG, "Application BaseContext: " + getApplication().getBaseContext());
Log.i(TAG, "Activity BaseContext: " + getBaseContext());
// 运行结果
E/MainActivity: Application: android.app.Application@4aadfda
E/MainActivity: ApplicationContext: android.app.Application@4aadfda
E/MainActivity: Activity: com.example.cc1over.cc1over.MainActivity@664ff5d
E/MainActivity: ActivityContext:com.example.cc1over.cc1over.MainActivity@664ff5d
E/MainActivity: Application BaseContext: android.app.ContextImpl@991ec0b
E/MainActivity: Activity BaseContext: android.app.ContextImpl@48f0ae8
可以看到,有以下几点不同:
- getApplication()和getApplicationContext()返回的是同一个对象,虽然同一块内存区域,但对象的类型不同:前者是Application,后者是Context。Java是强类型的语言,Application到Context相当于向上转型,会丢失掉一些接口的访问入口。
- 同理,Activity和Activity Context也是同一个对象,不同的类型。
- Application和Activity的Base Context都是ContextImpl对象,正是这个Context真正的实现类,被外围的修饰器包装了一下,才形成不同功能的类。