背景
最近有同学反馈在Application的 attachBaseContext(Context base)回调中调用 base.getApplicationContext() 会返回null。那我们借这个机会分析下Context的加载流程,并搞清楚getApplicationContext 到底是什么。
出错代码如下:
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
//此处获取的 applicationContext 对象为空
Context applicationContext = base.getApplicationContext();
}
Context继承关系
-
Context:是一个抽象类,定义了其必要的抽象功能方法;
-
ContextImpl:真正的Context 实现类;
-
ContextWrapper:Context包装类,其对外提供的能力均由 ContextImpl 提供,通过 attachBaseContext 与ContextImpl 建立绑定关系;
-
ContextThemeWrapper:带有主题功能的Context包装类;
-
Activity:这就是我们定义的Activity界面类了,其继承自ContextThemeWrapper;
-
Service:继承自ContextWrapper, 其内部也是引用ContextImpl;
-
Application:我们应用都可以自定义Application 对象,在进程启动生命周期回调等方法中做相关初始化等操作。
进程启动时序
创建主线程Looper并启动
这里我们就不介绍进程的fork 过程了,我们直接从ActivityThread 的main方法开始介绍(一般就是程序的入口)。
整个app的启动是分多步完成的,这里仅仅只是第一步而已,具体过程如下:
-
ActivityThread:main: app进程启动入口
-
Looper.prepareMainLooper(): 构建Looper,并与当前线程绑定
-
new ActivityThread():构建ActivityThread 实例
-
ActivityThread.attach():绑定应用程序
-
ActivityManager::getService():获取ActivityManagerService代理服务接口
-
ActivityManagerService::attachApplication: 通过AMS绑定新进程程序
-
ActivityThread.ApplicationThread::bindApplication:向主线程发起BIND_APPLICATION event事件
-
ViewRootImpl::addConfigCallback: 添加配置变化回调,统一由ViewRootImpl管理的。
-
Looper.loop():启动主线程循环,开始消费事件。
这里或许会有一个疑问,AMS在 attachApplication 的时候是怎么做到向新启动进程的主线程发送 BIND_APPLICATION 事件的呢?这里ActivityThread 和 AMS 通信原理如下图所示:本质上还是通过binder通信实现跨进程通信的。
此时我们其实还只是建立并启动了主线程Looper,并发起了BIND_APPLICATION 事件,接下来我们具体看看BIND_APPLICATION 事件中干了什么。
处理BIND_APPLICATION 事件
在ActivityThread中是通过 handleBindApplication 方法来处理BIND_APPLICATION 事件的。
类介绍:
-
ActivityThread: 程序启动入口,该类中启动主线程循环。
-
LoadedApk:加载的apk信息
-
ContextImpl:进程唯一的真实Context实现类。
-
Instrumentation:是ActivityThread 的一个内部类,主要用于进程创建等重要事件回调监控
-
Application:应用程序自定义Application 的基类。
流程介绍
1、构建 LoadedApk 对象
2、makeApplication
-
构建 ContextImpl 对象
-
newApplication。
-
反射构建app自定义的Application 对象
-
attach。
-
attachBaseContext。 Application的首个回调
-
为mLoadedApk赋值,完成Application和LoadedApk 的绑定关系
-
setOuterContext。 将Application对象赋值给outerContext成员变量。
-
mApplication = app。 将Application 对象赋值给 LoadedApk,如此两者互相持有其对象。
3、mInitialApplication = app。 将Application对象缓存到ActivityThread。
4、callApplicationOnCreate。直接回调Application 的onCreate 方法。
解答问题
为什么在attachBaseContext 中调用 getApplicationContext()会返回null 呢?
首先我们看看获取 getApplicationContext() 的逻辑
ContextImpl.java
final @NonNull LoadedApk mPackageInfo;
public Context getApplicationContext() {
return (mPackageInfo != null) ? mPackageInfo.getApplication() : mMainThread.getApplication();
}
由此可以看出 getApplicationContext 就是返回的 LoadedApk 和 ActivityThread 持有的Application 对象。
但是从上面 handleBindApplication处理流程 处理流程中可以看出分别赋值如下
-
LoadedApk: 在 newApplication 的最后一步 mApplication = app 赋值。
-
ActivityThread:在makeApplication 完成之后 mInitialApplication = app 赋值。
这两者赋值均在 attachBaseContext 回调之后, 所以在 attachBaseContext 中获取ApplicationContext 对象会为null。