Android上下文Context,学有所得

1,765 阅读8分钟

一、Context概览

ContextAndroid中,代表着当前应用程序运行环境的上下文,通过前面对Android应用程序的启动流程和四大组件的启动流程分析。发现只有ApplicationActivityService三者才会创建上下文Context。所以,我们也可以理解为ContextApplicationActivityService运行环境的上下文。所谓上下文,即可以根据一些变量、类来帮助我们获得相关资源、信息。类似聊天中,我们有时候需要前面沟通的信息,来理解当前对话的内容。从Android Studio继承窗口,我们找出Context的子类。

image.png

再根据前面分析启动流程中,ContextWrapper内部有一个指向Context类型的属性mBase,再实际运行会被赋值为ContextImpl实例。根据这些,我们画出主要的类图信息。

image.png

Context本身是一个抽象类,定义了通用的接口,这样我们在使用的时候,不会感知具体调用了哪些子类。即面向抽象,而不是具体。而Context的两个直接子类ContextWrapperContextImpl,我们在研究ApplicatonActivityService的启动流程,都会发现创建一个ContextImpl实例,并和三者绑定,也就是实例设置给ContextWrappermBase属性。而我们调用Context的一些能力,基本都会在ContextWrapper通过mBase转给ContextImpl对象去实现。ContextWrapper在这里理解为装饰者。而不需要主题相关的ApplicationService直接继承ContextWrapper,需要主题相关的Activity再经一层ContextThemeWrapper修饰。

本文分析的内容,可能和之前流程分析有些重复,但每篇文章的主题侧重点不用,分析的内容也不尽相同。

二、Application上下文

Application与Context的创建和关联

伴随着应用程序进程的启动,在执行主线程ActivityThread主函数main的时候,会执行ActivityThreadattach函数。

//ActivityThrad#main
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);

1、ActivityThread.attach

通过ContextImpl的静态函数createAppContext创建ContextImpl实例context,然后通过context去创建Application实例mInitialApplication,并回调mInitialApplicationonCreate生命周期函数。

private void attach(boolean system, long startSeq) {
    ...
    ContextImpl context = ContextImpl.createAppContext(
            this, getSystemContext().mPackageInfo);
    mInitialApplication = context.mPackageInfo.makeApplication(true, null);
    mInitialApplication.onCreate();
    ...
}
ContextImpl.createAppContext

createAppContext有两个重载函数。第2个重载函数直接new了一个ContextImpl实例。

static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
    return createAppContext(mainThread, packageInfo, null);
}

static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo,
        String opPackageName) {
    ...
    ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, null,0, null, opPackageName);
    context.setResources(packageInfo.getResources());
    context.mIsSystemOrSystemUiContext = isSystemOrSystemUI(context);
    return context;
}

关注一下ContextImpl的构造函数,然后ContextImpl的实例拥有应用程序相关资源的引用,成为调用其他资源的入口。

private ContextImpl(@Nullable ContextImpl container, @NonNull ActivityThread mainThread,
        @NonNull LoadedApk packageInfo, @Nullable String attributionTag,
        @Nullable String splitName, @Nullable IBinder activityToken, @Nullable UserHandle user,
        int flags, @Nullable ClassLoader classLoader, @Nullable String overrideOpPackageName) {
    //未与Application,或者是Activity、Service绑定,将mOuterContext先指向自己,
    mOuterContext = this;
    ...
    //接下来,将应用程序运行的相关信息,引用赋值给ContextImpl的属性
    //这样子,通过Context这个入口,就可以访问到相关资源
    mMainThread = mainThread;
    mToken = activityToken;
    mFlags = flags;

    if (user == null) {
        user = Process.myUserHandle();
    }
    mUser = user;
    //LoadedApk赋值
    mPackageInfo = packageInfo;
    mSplitName = splitName;
    mClassLoader = classLoader;
    mResourcesManager = ResourcesManager.getInstance();

    String opPackageName;

    if (container != null) {
        mBasePackageName = container.mBasePackageName;
        opPackageName = container.mOpPackageName;
        setResources(container.mResources);
        mDisplay = container.mDisplay;
        mIsAssociatedWithDisplay = container.mIsAssociatedWithDisplay;
        mIsSystemOrSystemUiContext = container.mIsSystemOrSystemUiContext;
    } else {
        mBasePackageName = packageInfo.mPackageName;
        ApplicationInfo ainfo = packageInfo.getApplicationInfo();
        if (ainfo.uid == Process.SYSTEM_UID && ainfo.uid != Process.myUid()) {
            opPackageName = ActivityThread.currentPackageName();
        } else {
            opPackageName = mBasePackageName;
        }
    }

    mOpPackageName = overrideOpPackageName != null ? overrideOpPackageName : opPackageName;
    mAttributionTag = attributionTag;
    //前面研究ContentProvider启动流程,mContentResolver初始化的地方
    mContentResolver = new ApplicationContentResolver(this, mainThread);
}

2、LoadedApk.makeApplication

回到attach函数,调用了contextLoadedApk类型的mPackageInfo属性makeApplication函数。其中mPackageInfoContextImpl的构造函数中被赋值。说实话,我确实不知道这样为啥要先创建ContextImpl实例context,然后再通过context去调LoadedApk实例的makeApplication函数。

分析一:创建Application的上下文ContextImpl类型的appContext

分析二:创建Application实例appapp绑定appContext

分析三:appContext绑定app

//LoadedApk#makeApplication xxm
public Application makeApplication(boolean forceDefaultAppClass,
        Instrumentation instrumentation) {
    if (mApplication != null) {
        return mApplication;
    }

    Application app = null;

    String appClass = mApplicationInfo.className;
    //没有自定义Application,就使用默认的
    if (forceDefaultAppClass || (appClass == null)) {
        appClass = "android.app.Application";
    }

    ...

    final java.lang.ClassLoader cl = getClassLoader();

    ...
    //这里才是真正创建Application上下文的地方,所以上面创建Context实例的作用是?
    //分析一
    ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);

    ...
    //分析二
    app = mActivityThread.mInstrumentation.newApplication(
            cl, appClass, appContext);
    //分析三
    appContext.setOuterContext(app);
    ...
    mActivityThread.mAllApplications.add(app);
    //通过AMS创建返回的赋值给mApplication
    mApplication = app;

    if (instrumentation != null) {
        ...
        instrumentation.callApplicationOnCreate(app);
        ...
    }
    return app;
}

makeApplication开始,贴一下时序图。

image.png

分析一,创建ContextImpl的实例可以参考第1小节。

3、Instrumentation.newApplication

在分析二中调用了InstrumentationnewApplication函数创建了Application的实例app,并关联上下文context

public Application newApplication(ClassLoader cl, String className, Context context)
        throws InstantiationException, IllegalAccessException, 
        ClassNotFoundException {
    Application app = getFactory(context.getPackageName())
            .instantiateApplication(cl, className);
    app.attach(context);
    return app;
}

getFactory函数返回的是AppComponentFactory类型的实例,其instantiateApplication函数,通过反射创建Application实例。

public @NonNull Application instantiateApplication(@NonNull ClassLoader cl,
        @NonNull String className)
        throws InstantiationException, IllegalAccessException, ClassNotFoundException {
    return (Application) cl.loadClass(className).newInstance();
}

而在创建的Application类型的app之后,newApplication函数调用的appattach绑定上下文context。因为Application本身继承自ContentWrapper,所以自身也是个上下文。

Application的attach函数的主要作用就是将makeApplication函数创建的ContextImpl实例appContext一路传递进来,设置给Application的父类ContextWrappermBase属性。也就是说,现在Application关联到了ContextImpl类型的上下文mBase。后续我们通过Application调用Context相关的能力,就会转移到了ContextImpl的实例mBase

final void attach(Context context) {
    attachBaseContext(context);
    mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}

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

4、ContextImpl.setOuterContext

makeApplication函数分析三中调用了appContextsetOuterContext函数。ContextImpl类有一个Context类型的mOuterContext属性。在调用ContextImpl构造函数的时候,该属性指向了自己。而setOuterContext函数将该属性指向Application实例。

final void setOuterContext(Context context) {
    mOuterContext = context;
}

到这里,ApplicationContextImpl就互相关联。

ApplicationContext的使用

我们在ContextWrapper的子类ApplicationActivityService都可以通过getApplicationContext函数来获取Application的上下文。

public Context getApplicationContext() {
    return mBase.getApplicationContext();
}

通过上面的分析,我们知道mBase的实际类型是ContextImpl

public Context getApplicationContext() {
    return (mPackageInfo != null) ?
            mPackageInfo.getApplication() : mMainThread.getApplication();
}

这里的LoadedApk类型的mPackageInfo肯定不为null,调用了LoadedApk实例的getApplication函数。

Application getApplication() {
    return mApplication;
}

mApplication正是前面调用LoadedApk.makeApplication函数,创建的Applicaiton实例,Application本身也继承Context。所以我们通过getApplicationContext获得了应用的上下文,调用相关Context的能力都会转给Application持有的实际是ContextImpl类型的mBase对象。

image-20230321234942974

三、Activity的上下文

在研究Activity的启动流程过程,最终会走到ActivityThreadperformLaunchActivity函数。

    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
		
		...
		//分析一:创建ContextImpl实例
        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        ...
        java.lang.ClassLoader cl = appContext.getClassLoader();
        //分析二:创建Activity实例
        activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);

        ...
             
        Application app = r.packageInfo.makeApplication(false, mInstrumentation);
		//分析三:appContext关联Activity
        appContext.setOuterContext(activity);
		//分析四:activity关联appContext
		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,
                r.assistToken);

        ...
        return activity;
    }

Activity上下文的创建和Application上下文的创建是类似的。分析一通过createBaseContextForActivity创建ContextImpl的实例appContext;分析二创建Activity的实例activity,这部分可以参考Activity的启动流程最后部分。分析三:将activity设置给appContext,这里appContextmBase就指向了activity。分析四调用activityattach方法,关联appContext

image.png

ActivityThread.createBaseContextForActivity

分析一:通过createBaseContextForActivity函数为当前Activity创建一个ContextImpl类型的上下文实例appContext。调用了createActivityContext函数。

private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
    ...
    ContextImpl appContext = ContextImpl.createActivityContext(
            this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
    ...
    return appContext;
}

ContextImpl.createActivityContext

createActivityContext函数同样是通过ContextImpl构造函数创建ContextImpl实例,然后多设置了更多属性。

static ContextImpl createActivityContext(ActivityThread mainThread,LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId, Configuration overrideConfiguration) {
    
    ...

    ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null,
            activityInfo.splitName, activityToken, null, 0, classLoader, null);
    context.mIsUiContext = true;
    context.mIsAssociatedWithDisplay = true;
    context.mIsSystemOrSystemUiContext = isSystemOrSystemUI(context);

    displayId = (displayId != Display.INVALID_DISPLAY) ? displayId : Display.DEFAULT_DISPLAY;

    final CompatibilityInfo compatInfo = (displayId == Display.DEFAULT_DISPLAY)
            ? packageInfo.getCompatibilityInfo()
            : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;

    final ResourcesManager resourcesManager = ResourcesManager.getInstance();

    context.setResources(resourcesManager.createBaseTokenResources(activityToken,
            packageInfo.getResDir(),
            splitDirs,
            packageInfo.getOverlayDirs(),
            packageInfo.getApplicationInfo().sharedLibraryFiles,
            displayId,
            overrideConfiguration,
            compatInfo,
            classLoader,
            packageInfo.getApplication() == null ? null
                    : packageInfo.getApplication().getResources().getLoaders()));
    context.mDisplay = resourcesManager.getAdjustedDisplay(displayId,
            context.getResources());
    return context;
}

Activity.attach

分析四:在创建ContextImpl实例appContextActivity实例activity后,将activity关联到appContext。然后调用activityattach函数,将appContext关联到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, IBinder assistToken) {
    ...
    attachBaseContext(context);
    ...
}

调用了attachBaseContext函数。

protected void attachBaseContext(Context newBase) {
    super.attachBaseContext(newBase);
}
//ContextThemeWrapper xxm
protected void attachBaseContext(Context newBase) {
    super.attachBaseContext(newBase);
    ...
}
//ContextWrapper xxm
protected void attachBaseContext(Context base) {
	...
    mBase = base;
}

appContext经过一路传递最终还是设置给了Activity的父类ContextWrappermBase变量。

到这里ActivityContext就相关关联了。后续在Activity关于Context的相关操作都会传递到实际类型是ContextImpl的mBase中去 。

四、Service的上下文

Service的上下文,其实在Service的启动流程已经分析过了,为了Context的完整性,摘抄过来。

Service的启动流程的最后,调用了ActivityThreadhandleCreateService函数。看完handleCreateService函数之后,其实已经不用再进一步仔细分析了,和ApplicationActivity的上下文太惊人的相似。

    private void handleCreateService(CreateServiceData data) {
		...
        Service service = null;
			//分析一
            ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
            
            Application app = packageInfo.makeApplication(false, mInstrumentation);
            java.lang.ClassLoader cl = packageInfo.getClassLoader();
            
            service = packageInfo.getAppFactory()
                    .instantiateService(cl, data.info.name, data.intent);
            ...
			//分析二
            context.setOuterContext(service);
            //分析三
            service.attach(context, this, data.info.name, data.token, app,
                    ActivityManager.getService());
           	...
          
    }

分析一:通过ContextImpl的静态函数createAppContext返回了一个ContextImpl类型的contextcreateAppContext又调用了重载函数createAppContext。直接新建了ContextImpl实例context,构造函数传递了ActivityThread类型的mainThreadLoadedApk类型的packageInfo。并给context设置了资源环境和是否Syetem属性。

static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
    return createAppContext(mainThread, packageInfo, null);
}

static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo,
        String opPackageName) {
    if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
    ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, null,
            0, null, opPackageName);
    context.setResources(packageInfo.getResources());
    context.mIsSystemOrSystemUiContext = isSystemOrSystemUI(context);
    return context;
}

回到handleCreateService函数的分析二,在创建好Service对象service之后,将service作为参数传递给了context.setOuterContext函数。Service本身继承自ContextWrapper,ContextWrapper又是Context的子类。这时候的setOuterContext函数将service设置给了contextmOuterContext属性。意味着当前上下文context持有当前新建的service引用。

在分析三,调用了service.attach函数,context并作为第一个参数被传入。attach函数又调用了attachBaseContext函数。后面的分析就省略了。

public final void attach(
        Context context,
        ActivityThread thread, String className, IBinder token,
        Application application, Object activityManager) {
    attachBaseContext(context);
	...
}

五、总结

一个应用程序程序在运行的过程上下文的数量=Application+Activity+Service的数量。之所以能称为上下文,是因为Context的实现类ContextImpl持有应用程序运行过程相关资源类应用,Context作为一个入口,通过ApplicationActivityService的修饰下,更加方便的访问这些资源。