关于 Android Context 知道这些就可以了
前言
对于 Context 总是觉得很常见,却又很陌生。平时项目开发几乎在哪都可以获取到,但用起来又有所差别,那究竟怎么用才是正确的?其实它并没有很神秘,只要我们层层剥离,Context 会是我们最熟悉的伙伴。在往下看之前建议先看看这两篇做个铺垫
理解 Android 的 Context
我问你答-简述 Application 和 Activity 的 Context
从设计上来说说几个类的关系
从相关的几个类的关系上看,Application, Activity, Service 都继承自 ContextWrapper, 后者提供了很多方法供子类在具体场景下使用。不过ContextWrapper 内部并没有自己去实现,而是通过 ContextImpl 类型的成员变量 mBase 来实现的。「Android 进阶解密」中称这种设计为装饰模式(关于设计模式后面还要梳理,暂且按书上的),不过不管归为哪种设计模式,这种实现的优点其实显而易见。
ContextWrapper 内部实现对子类不可见,假如实现逻辑变了换掉 mBase,改用别的,这对 Application 他们完全没有影响,因为他们都感知不到;其次,子类没有直接继承真正的实现类,这使得子类与实现类耦合度降低,便于实现类扩展;还有就是子类在获取 Context 时也变得很灵活。
细谈 mBase 分别是怎么来的
- Application
//LoadedApk
public Application makeApplication(boolean forceDefaultAppClass,Instrumentation instrumentation) {
//用于Application对象内的mBase对象赋值,注意方法名称
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
app = mActivityThread.mInstrumentation.newApplication(cl, appClass, appContext);
}
//Instrumentation
public Application newApplication(ClassLoader cl, String className, Context context) {
Application app = getFactory(context.getPackageName()).instantiateApplication(cl, className);
//这里传入的 context 就是 Application 调用 *getBaseContext* 获取到的 mBase
app.attach(context);
return app;
}
在 震惊!Android 启动应用进程竟不是从 AMS 开始 里其实我们分析过 Application 里的 mBase 是怎么来的。
- Activity
//ActivityThread
public Activity handleLaunchActivity(ActivityClientRecord r, PendingTransactionActions pendingActions, Intent customIntent) {
WindowManagerGlobal.initialize();
final Activity a = performLaunchActivity(r, customIntent);
return a;
}
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
//这里创建出 Activity 对象实例,这个过程类似于 Application 对象实例的创建
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
}
try {
//这里我是有疑问的,因为在启动应用进程时会调用 handleBindApplication 方法
//在那个方法里也会创建 Application 对象实例,并且也会执行 onCreate
//而这里也会创建并执行 onCreate,难道启动一个 Activity 就会执行一遍 Application 的方法?
//这显然是不可能的
//有一点不同的是,bindApplication 时 mInitialApplication 这个成员变量会被赋值
//而这里仿佛会用于 activity 执行 attach 方法。
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
if(activity != null) {
Window window = null;
if(r.mPendingRemoveWindow != null && r.mPreserveWindow) {
window = r.mPendingRemoveWindow;
}
//做 attach 操作,这里传入的 appContext 就是 getBaseContext 方法返回的 mBase 对象
activity.attach(appContext, this, getInstrumentation(), r.token, r.ident, app, r.intent, r.activityInfo, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config, r.referrer, r.voiceInteractor, window, r.configCallback);
//执行 activity onCreate 方法
mInstrumentation.callActivityOnCreate(activity, r.state);
}
}
return activity;
}
在 根 Activity 的启动后续 里,我们也留意过 Activity 里面的 mBase 对象是如何赋值的,这里和 Application 那个的创建就不一样了,你会发现这个是和 Activity 相关的,可以理解为 Activity 专用的。
- Service
//ActivityThread
private void handleCreateService(CreateServiceData data) {
//这里应该很熟悉了,在创建 Application 和 Activity 都遇到过
//代表着应用包信息,如果是同一个应用那么所指向的对象引用都是一致的
LoadedApk packageInfo = getPackageInfoNoCheck(
data.info.applicationInfo, data.compatInfo);
Service service = null;
try {
//这种方式和创建 Activity 也很类似
java.lang.ClassLoader cl = packageInfo.getClassLoader();
service = packageInfo.getAppFactory()
.instantiateService(cl, data.info.name, data.intent);
} catch (Exception e) {
if (!mInstrumentation.onException(service, e)) {
throw new RuntimeException(
"Unable to instantiate service " + data.info.name
+ ": " + e.toString(), e);
}
}
ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
Application app = packageInfo.makeApplication(false, mInstrumentation);
//Service 也会关联 Context,因为 Service 也继承自 ContextWrapper
//这个 context 就是 Service 调用 getBaseContext 方法返回的对象 mBase
service.attach(context, this, data.info.name, data.token, app,
ActivityManager.getService());
//调用 service 的 onCreate
service.onCreate();
//把 service 存起来
mServices.put(data.token, service);
}
在 Android 进阶解密阅读笔记4 中我们也找到了对应 ContextImpl 的创建和赋值。这里就和 Application 那个没什么区别了,因为调用的方法相同。
其实可以发现他们(Application, Activity, Service)都是通过在相应对象创建后,做 attach 过程的时候赋值,可见源码实现上思路都是想通的,看懂这快代码,相关的代码其实看起来也快的。
setOuterContext
关注这个方法是因为 BroadcastReceiver 的 onReceive 方法有个 Context 类型入参,那么这个参数值代表什么呢?
就从 registerReceiver 方法说起,从动态注册广播的过程中可以知道 onReceive 方法入参的值是接收分发器 ReceiverDispatcher 对象里的变量,而这个变量又是新建时通过构造方法赋值的,最后层层向上追踪可以知道它来自于 ContextImpl 对象的 mOuterContext 变量,它是通过 set 方法赋值的。
那再来想下,假如我们是在 Activity 里调用了 registerReceiver 方法,那么这个 ContextImpl 对象就是 Activity 的 mBase 变量,它的创建生成过程我们是知道的,即在创建 Activity 时创建并赋值的。重新回看下 Activity 创建时的那段代码,就会发现 ContextImpl 对象会在 activity 做 attach 操作前一步调用 setOuterContext 方法,将 activity 传入。
所以,onReceive 方法的 Context 类型入参就是注册广播接收器时的 Activity 对象实例。同样的道理 Application 对象里的 mBase 变量也有一个 mOuterContext 变量指向的就是 Application 对象,Service 对象里的 mBase 变量也有一个 mOuterContext 变量指向的就是 Service 对象。归根到底可能应用里的 Context 对象实例就这么些,只不过相互引用罢了。