App启动之Context加载过程

101 阅读3分钟

背景

最近有同学反馈在Application的 attachBaseContext(Context base)回调中调用 base.getApplicationContext() 会返回null。那我们借这个机会分析下Context的加载流程,并搞清楚getApplicationContext 到底是什么。

出错代码如下:


@Override
protected void attachBaseContext(Context base) {  
    super.attachBaseContext(base);   
    //此处获取的 applicationContext 对象为空
    Context applicationContext = base.getApplicationContext();
}

Context继承关系

image.png

  • Context:是一个抽象类,定义了其必要的抽象功能方法;

  • ContextImpl:真正的Context 实现类;

  • ContextWrapper:Context包装类,其对外提供的能力均由 ContextImpl 提供,通过 attachBaseContext 与ContextImpl 建立绑定关系;

  • ContextThemeWrapper:带有主题功能的Context包装类;

  • Activity:这就是我们定义的Activity界面类了,其继承自ContextThemeWrapper;

  • Service:继承自ContextWrapper, 其内部也是引用ContextImpl;

  • Application:我们应用都可以自定义Application 对象,在进程启动生命周期回调等方法中做相关初始化等操作。

进程启动时序

创建主线程Looper并启动

这里我们就不介绍进程的fork 过程了,我们直接从ActivityThread 的main方法开始介绍(一般就是程序的入口)。

app启动过程1.png

整个app的启动是分多步完成的,这里仅仅只是第一步而已,具体过程如下:

  1. ActivityThread:main: app进程启动入口

  2. Looper.prepareMainLooper(): 构建Looper,并与当前线程绑定

  3. new ActivityThread():构建ActivityThread 实例

  4. ActivityThread.attach():绑定应用程序

  5. ActivityManager::getService():获取ActivityManagerService代理服务接口

  6. ActivityManagerService::attachApplication: 通过AMS绑定新进程程序

  7. ActivityThread.ApplicationThread::bindApplication:向主线程发起BIND_APPLICATION event事件

  8. ViewRootImpl::addConfigCallback: 添加配置变化回调,统一由ViewRootImpl管理的。

  9. Looper.loop():启动主线程循环,开始消费事件。

这里或许会有一个疑问,AMS在 attachApplication 的时候是怎么做到向新启动进程的主线程发送 BIND_APPLICATION 事件的呢?这里ActivityThread 和 AMS 通信原理如下图所示:本质上还是通过binder通信实现跨进程通信的。

ActivityThread和AMS通信.png

此时我们其实还只是建立并启动了主线程Looper,并发起了BIND_APPLICATION 事件,接下来我们具体看看BIND_APPLICATION 事件中干了什么。

处理BIND_APPLICATION 事件

在ActivityThread中是通过 handleBindApplication 方法来处理BIND_APPLICATION 事件的。

handleBindApplication处理流程.png

类介绍:

  • 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。