Android Context源码探究

Context可以说是Android开发中非常高频使用的内容了。弹出Dialog、Toast;打开新的Activity,包括获取项目内resource资源都需要Context对象。那么Context到底是个啥,为啥Android中这么多的操作都需要Context来完成。

Context是个啥

Context本身首先是一个抽象类,我们对它常用的称呼是上下文,官方的注释是这么说的

/**
 * Interface to global information about an application environment.  This is
 * an abstract class whose implementation is provided by
 * the Android system.  It
 * allows access to application-specific resources and classes, as well as
 * up-calls for application-level operations such as launching activities,
 * broadcasting and receiving intents, etc.
 */

大概的意思就是Context是一个与应用环境相关的全局信息接口,由Android系统提供实现的抽象类。那么Context内部到底都做了什么才让自己变成一个“海王”,让我们的项目里处处都有它的身影呢?

首先总览一下Context的内容构成:

  • 自定义注解 => 主要用来表达{模式}、{标识}、{类型}之类的概念
  • 静态常量 => 主要配合自定义注解使用,用来定义注解内的参数范围
  • 抽象方法 => 占Context的主要部分,Context内定义了大量的抽象方法,向外部提供了非常多的功能调用,例如我们比较熟悉的getString(resId)startActivity(intent)startService(intent) 等等,内部各类抽象方法提供了非常丰富的能力,这也就是Context在项目中随处可见的一部分原因。
  • 实现方法 => 数量较少,但也存在,提供一些基础能力。getMainExecutor()的实现与子类重写

从这些构成来看,Context并不像是个上下文,不能说毫不相关只能说是莫名其妙。

哪里都看不出这是个什么承上启下的内容,甚至它是个抽象类,也没有继承实现任何类;它所承担的能力更多是声明功能和承载信息,提供给自己的实现类;Context更像是一个信息工具集合体,作为一个Application广泛认同的内容承载信息,声明功能范围。


Context的实现类

有些朋友可能发现了,上面在举例抽象方法构成时提到的一些方法大家都用过,但是使用的时候有时候要用context调用有时候直接调用就行了,莫非Context是个什么全局基类?[・_・?]

实际上这个和我们的使用场景有关,Context直接的实现类有两个:ContextImpl、ContextWrapper,而这两个类中实际干活的类只有ContextImpl,ContextWrapper是ContextImpl的装饰类,在它内部有一个mBase的字段,实际就是指向ContextImpl实例的对象。

//ContextWrapper源码节选

Context mBase;

protected void attachBaseContext(Context base) {
    if (mBase != null) {
        throw new IllegalStateException("Base context already set");
    }
    mBase = base;
}

@Override
public void startActivity(Intent intent) {
    mBase.startActivity(intent);
}

Context的实现关系并没有到此为止,接下来登场的就是大家熟悉的老朋友了,有请冯巩老师(不是, ContextWrapper后续的继承类分别是ApplicationServiceContextThemeWrapper,而ContextThemeWrapper则专门封装了getTheme的内容,不用多想,ContextThemeWrapper的子类就是Activity,至此,Context整个家族都到齐了。

VejqSI.png

梳理一遍Context的设计,Context作为基类声明了抽象方法,由ContextImpl实现其中的方法,接着ContextWrapper作为装饰类引用ContextImpl的实例,而Service、Application、Activity都直接或间接的集成ContextWrapper,并在此基础上进行了一些拓展。

好了,到这似乎都清楚了。
Context继承关系
了吗?

虽然理清了整个继承关系,但是在上面的代码里展示的很明显,startActivity里实际执行的是mBase对象的方法,也就是ContextImpl的对象,那么mBase得到赋值的时机也就成为了ContextImpl生效的关键。


Context是如何起作用的

Context生效是通过层层实现来达到目的的,也就是说使用者将ContextImpl实例赋值给自己的mBase后,就可以随意使用Context的实现了。

接下来就是追根溯源时间

Application Context准备过程

ApplicationContext需要在Application应用启动后就能够使用,至少我们在Application的onCreate中就已经在使用Context了,那么就需要从应用启动开始排查Context相关的内容。

前面太多的源码加上Android10里LifeCycle纠缠太深,我还没有研究明白;就从最贴近创建的部分来看吧

// ActivityThread代码节选
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    //...省略内容...
    try {
        Application app = r.packageInfo.makeApplication(false, mInstrumentation);
    }
    //...省略内容...
    return activity;
}

这就是在启动桌面图标,启动首页Activity过程中创建Application的动作,r.packageInfo指向的是一个LoadedApk对象,通过它内部定义的方法,使用mInstrumentation就可以创建出我们的Application对象。

等等,朋友,你这车轮都挂树上去了。

这个LoadedApk和我们的Context有啥关系?你信不信我
在这里插入图片描述

别急别急,创建Application绕不过它,我们先看看创建Application的过程。

// LoadedApk源码节选
public Application makeApplication(boolean forceDefaultAppClass,Instrumentation instrumentation) {
    // 这里可以看到LoadedApk里维护了一个application对象,避免重复创建
    if (mApplication != null) {
        return mApplication;
    }

    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "makeApplication");

    Application app = null;

    //...省略部分...

    try {
        //...省略部分...
        
        //创建ContextImpl实例,之后使用Instrumentation创建Application,传入context对象
        ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
        app = mActivityThread.mInstrumentation.newApplication(cl, appClass, appContext);
        appContext.setOuterContext(app);
    }
    //...省略部分...
    
    // 刚刚创建的Application直接保存起来了
    mApplication = app;
    
    return app;
}

上面的过程可以看到,Context会调用createAppContext来创建Application使用的Context,其中参数就包含了LoadedApk对象本身。我们再来看看createAppContext做了什么工作。

// ContextImpl源码节选
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
    if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
    // 传入的LoadedApk在ContextImpl构造函数中会赋值给全局变量{mPackageInfo},后续部分获取Application相关内容的方法会通过它来完成
    ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0,null);
    context.setResources(packageInfo.getResources());
    return context;
}

获取到传入的LoadedApk对象后,直接实例化ContextImpl对象,并且会赋值给全局变量{mPackageInfo};在ContextImpl中也能看到部分方法对mPackageInfo的引用,感兴趣的朋友可以翻翻ContextImpl的源码,这里就不展开了。上面的内容主要是看看Context和LoadedApk之间的联系。


接下来是Application的创建设置ContextImpl实例

// Instrumentation源码节选
public Application newApplication(ClassLoader cl, String className, Context context)
            throws InstantiationException, IllegalAccessException, 
            ClassNotFoundException {
    // AppComponentFactory最终指向的是AppComponentFactory,内部的实现是
    // return (Application) cl.loadClass(className).newInstance();
    Application app = getFactory(context.getPackageName()).instantiateApplication(cl, className);
    app.attach(context);
    return app;
}

好家伙,终于是找到了,在Application创建完成以后,直接用Application对象调用attach(ContextImpl实例),通过attach方法把ContextImpl传入了Application中,最后来看看Application的attach实现吧。

// Application源码节选
final void attach(Context context) {
    attachBaseContext(context);
    mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}

// ContextWrapper源码节选
protected void attachBaseContext(Context base) {
    if (mBase != null) {
        throw new IllegalStateException("Base context already set");
    }
    mBase = base;
}

这个过程需要回忆一下上面提到的,Application是继承于ContextWrapper的,在调用attachBaseContext时传入的是ContextImpl的实例,通过这个方法完成了装饰类使用实例的设置。至此Application创建及其Context创建、配置的过程就走完一遍了,整过过程还算清晰,粗略的可以分成3步,就和大象进冰箱一样

  1. Application实例创建
  2. ContextImpl实例创建
  3. Application设置ContextImpl实例

在此之后,Application中就可以随意调用Context抽象类中定义的方法了,所有的活ContextImpl都会认真负责的搞定;但在Application的attach之前,ContextImpl是没有到岗的,因此attach之后才是调用Context的正确时机。


Activity Context准备过程

看过了Application创建的过程,我们再来看看Activity的Context是如何创建的,之前讲到Application创建时大家就应该发现了,创建Application的方法名反而是performLaunchActivity,我们再来细致的看一看这个方法。

// ActivityThread源码节选
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ActivityInfo aInfo = r.activityInfo;
    
    // ...省略部分...    

    ContextImpl appContext = createBaseContextForActivity(r);
    Activity activity = null;
    try {
        java.lang.ClassLoader cl = appContext.getClassLoader();
        activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
        // ...省略部分...   
    } catch (Exception e) {
        // ...省略部分...   
    }

    try {
        Application app = r.packageInfo.makeApplication(false, mInstrumentation);

        // ...省略部分...   

        if (activity != null) {
            // ...省略部分...   
            appContext.setOuterContext(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, r.configCallback);
            // ...省略部分...   
        }
        // ...省略部分...   
        return activity;
}
    
private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
    // ...省略部分..

    ContextImpl appContext = ContextImpl.createActivityContext(this, r.packageInfo,
        r.activityInfo, r.token, displayId, r.overrideConfig);

    // ...省略部分..
    return appContext;
}
    

又是熟悉的方法,流程也和Application基本一致,可以看到Activity使用的Context是由ContextImpl调用的createActivityContext创建的;到这我们可以看出来,ContextImpl对和Activity提供了特有的初始化方法,在创建方法内部还单独配置了resource对象,感兴趣的朋友可以深入了解一下。

值得注意的是,AppContext实例化以后,通过setOuterContext将Activity对象赋值给了自己的mOuterContext对象,在Activity的attach之后,Activity和ContextImpl将互相持有对方的引用,Activity父类中的mBase和ContextImpl中的mOuterContext。

最后不出意外,activity的Context也是在attach中赋值的。

// Activity源码节选
 final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback) {
        
    attachBaseContext(context);
    // ...省略部分...
}

在attachBaseContext调用之后,Activity中的Context也就获取到了ContextImpl实例,之后就能够随意调用Context方法了。


Service Context准备过程

相对另外两个Context实现类来说,Service Context准备的过程可以说是非常的直接了

// Activity源码节选
private void handleCreateService(CreateServiceData data) {
    // ...省略部分...
    try {
        if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);

        ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
        context.setOuterContext(service);

        Application app = packageInfo.makeApplication(false, mInstrumentation);
        service.attach(context, this, data.info.name, data.token, app,
                ActivityManager.getService());
        service.onCreate();
        // ...省略部分...
    } catch (Exception e) {
        if (!mInstrumentation.onException(service, e)) {
            throw new RuntimeException("Unable to create service " + data.info.name + ": " + e.toString(), e);
        }
    }
}

整个过程的主要部分没有什么特殊的,同样是在attach之后完成的ContextImpl赋值。


至此Context和它的一大家子我们都有了一定的了解了,关于Context具体做能做什么就需要我们更深入的去看Context的内容,要知道它是怎样实现的我们可以到ContextImpl中找到答案;如果和它的使用场景有关,就需要区分Application、Activity、Service来独立的分析。

完整的了解一次Context过程,收获颇丰,在梳理各个环节时,因为是初次接触,会遇到各个让人困惑的节点,有的和设计思路有关,有的和应用开发中的问题有关。虽然没有吧所有问题都完全解决,毕竟源码可以说是海量,但对之前熟练用的工具有了新的认识。不能说完全没用,只能说毫无意义(╯°□°)╯︵ ┻━┻ 开个玩笑,提升之路任重道远,下篇总结见。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值