android 技术梳理

关于 Android Context 知道这些就可以了

2021-02-03  本文已影响0人  jkwen

前言

对于 Context 总是觉得很常见,却又很陌生。平时项目开发几乎在哪都可以获取到,但用起来又有所差别,那究竟怎么用才是正确的?其实它并没有很神秘,只要我们层层剥离,Context 会是我们最熟悉的伙伴。在往下看之前建议先看看这两篇做个铺垫

理解 Android 的 Context
我问你答-简述 Application 和 Activity 的 Context

从设计上来说说几个类的关系

从相关的几个类的关系上看,Application, Activity, Service 都继承自 ContextWrapper, 后者提供了很多方法供子类在具体场景下使用。不过ContextWrapper 内部并没有自己去实现,而是通过 ContextImpl 类型的成员变量 mBase 来实现的。「Android 进阶解密」中称这种设计为装饰模式(关于设计模式后面还要梳理,暂且按书上的),不过不管归为哪种设计模式,这种实现的优点其实显而易见。

ContextWrapper 内部实现对子类不可见,假如实现逻辑变了换掉 mBase,改用别的,这对 Application 他们完全没有影响,因为他们都感知不到;其次,子类没有直接继承真正的实现类,这使得子类与实现类耦合度降低,便于实现类扩展;还有就是子类在获取 Context 时也变得很灵活。

细谈 mBase 分别是怎么来的

//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 是怎么来的。

//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 专用的。

//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 对象实例就这么些,只不过相互引用罢了。

上一篇下一篇

猜你喜欢

热点阅读