Context创建过程

Context意为上下文,是一个应用程序环境信息的接口,Android中的四大组件都会涉及Context,在开发中我们经常会使用到Context。使用Context来调用方法,比如:启动Activity,访问资源,调用系统资源等。或者在调用方法时传入Context,比如:创建Dialog,弹出Toast等。下面我们就看一下Ccontext是如何创建的。

Context相关类

Context本身是一个抽象类,它内部定义类很多方法以及静态变量,它的具体实现类是ContextImpl。先看一下和Context相关联的类有哪些:

从上面的关系图中我们可以看出,ContextImplContextWrapper都继承自ContextContextWrapper内部包含Context类型的mBase对象,mBase对象具体指向ContextImpl,这里使用类装饰模式,ContextWrapper就是装饰类,它对ContextImpl进行包装,它几乎所有的方法都是调用ContextImpl的相应方法来实现的。ContextThemeWrapper、Application、Service都继承自ContextWrapper,这样它们都可以通过mBase来使用Context的方法,同时它们也是装饰类,在ContextWrapper的基础上又添加来不同的功能。ContextThemeWrapper中包含和主题相关的方法,因此需要主题的Activity继承自ContextThemeWrapper

从上面的关系图我们还可以知道一个应用中几个Context,在应用程序中Context的具体实现子类就是:Activity,Service,Application。那么Context数量=Activity数量+Service数量+1。Broadcast Receiver,Content Provider并不是Context的子类,他们所持有的Context都是其他地方传过去的,所以并不计入Context总数。

Context作用域

上面我们说到由于Context的具体实例是由ContextImpl类去实现的,因此在绝大多数场景下,Activity、Service和Application这三种类型的Context都是可以通用的。不过有几种场景比较特殊,比如启动Activity,还有弹出Dialog。出于安全原因的考虑,Android是不允许Activity或Dialog凭空出现的,一个Activity的启动必须要建立在另一个Activity的基础之上,也就是以此形成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),因此在这种场景下,我们只能使用Activity类型的Context,否则将会出错。

非Activity类型的Context并没有所谓的任务栈,所以待启动的Activity就找不到栈了。就会报错,

从上面我们知道一个应用中的Context类型有:ActivityServiceApplication,下面我们就看一下它们创建的过程。

Application Context的创建过程

在一个应用启动完成以后,应用程序就会有一个全局的Application Context,在Activity启动流程分析中知道在ApplicationThreadscheduleLaunchActivity方法中向H类发送LAUNCH_ACTIVITY类型的消息,在该类型消息的处理逻辑中最终对调用ActivityThreadperformLaunchActivity方法,在该方法中会先创建Application对象,然后在在启动Activity,下面就看一下和创建Application对象相关的代码:

frameworks/base/core/java/android/app/ActivityThread.java

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ......
    try {
        //创建Application
        Application app = r.packageInfo.makeApplication(false, mInstrumentation);
        ......
    }
    ......
    return activity;
}

ActivityClientRecord的成员变量packageInfoLoadedApk类型的,它是用来描述已加载的APK文件。接着我们看一下LoadedApkmakeApplication方法

frameworks/base/core/java/android/app/LoadedApk.java

public Application makeApplication(boolean forceDefaultAppClass,
        Instrumentation instrumentation) {
    //第一次启动,为空
    if (mApplication != null) {
        return mApplication;
    }
    ......
    try {
        java.lang.ClassLoader cl = getClassLoader();
        if (!mPackageName.equals("android")) {
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                    "initializeJavaContextClassLoader");
            initializeJavaContextClassLoader();
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        }
        //创建ContextImpl对象
        ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
        //创建Application对象,并将ContextImpl对象传入
        app = mActivityThread.mInstrumentation.newApplication(
                cl, appClass, appContext);
        //将Application对象赋值给ContextImpl的Context类型的成员变量mOuterContext
        appContext.setOuterContext(app);
    } catch (Exception e) {
       ......
    }
    mActivityThread.mAllApplications.add(app);
    //将创建的Application对象赋值给成员变量mApplication
    mApplication = app;
    ......
    return app;
}

在方法的开始如果mApplication不为空,则返回该变量,在第一次启动应用程序的时候该变量为空,下面的逻辑主要做了以下几件事:

  • 1.创建ContextImpl对象。
  • 2.创建Application对象并且关联ContextImpl对象。
  • 3.将Application对象赋值给ContextImpl的Context类型的成员变量mOuterContext,这样ContextImpl中就包含了Application的引用。
  • 4.将创建的Application对象赋值给LoadedApk的成员变量mApplicationmApplication代表Application Context

下面看一下Application是如何创建的,也就是InstrumentationnewApplication方法

frameworks/base/core/java/android/app/Instrumentation.java

static public Application newApplication(Class<?> clazz, Context context)
        throws InstantiationException, IllegalAccessException, 
        ClassNotFoundException {
    Application app = (Application)clazz.newInstance();
    app.attach(context);
    return app;
}

该方法的实现很简单,通过反射来创建Application,然后调用它的attach方法,调用该方法传入的参数context就是上面传入过来的ContextImpl对象。

frameworks/base/core/java/android/app/Application.java

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

在该方法中调用了attachBaseContext方法,该方法是在Application的父类ContextWrapper中实现,代码如下:

frameworks/base/core/java/android/content/ContextWrapper.java

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

在该方法中base就是一路传递过来的ContextImpl对象,将该对象赋值给ContextWrapper的成员变量mBase,这样在ContextWrapper中就可以使用Context的方法了,而Application继承自ContextWrapper,这样Application也可以使用Context的方法了。

Applicationattach方法的作用就是使Application可以使用Context方法,这样才能用Application来代表Application Context

Application Context的获取

我们通过getApplicationContext方法来获取Application Context,该方法是在ContextWrapper中实现的,代码如下:

frameworks/base/core/java/android/content/ContextWrapper.java

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

mBase指的是ContextImpl,接着看一下它的getApplicationContext方法

frameworks/base/core/java/android/app/ContextImpl.java

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

mPackageInfoLoadedApk类型的对象,我们在调用getApplicationContext方法时,应用程序已经启动,因此LoadedApk不为null,就会调用它的getApplication方法,该方法的代码如下:

Application getApplication() {
    return mApplication;
}

mApplication就是在我们上面提到的LoadedApkmakeApplication方法被赋值的,指向Application对象

平时在开发中我们获取Application对象还有一个方法就是:getApplication(),通过打印这两个方法返回值的内存地址,发现都是相同的,也就是说它们返回的是同一个对象,既然这两个方法得到的结果都是相同的,那么Android为什么要提供两个功能重复的方法呢?

实际上这两个方法在作用域上有比较大的区别。getApplication()方法的语义性非常强,一看就知道是用来获取Application实例的,但是这个方法只有在ActivityService中才能调用的到(该方法没有定义在ContextWrapper中)。那么也许在绝大多数情况下我们都是在Activity或者Service中使用Application的,但是如果在一些其它的场景,比如BroadcastReceiver中也想获得Application的实例,这时就可以借助getApplicationContext()方法了。

Activity的Context创建过程

要想在Activity中使用Context,必须要先创建ContextActivityContext是在Activity启动过程中被创建的,Activity的启动最终会调用
ActivityThreadperformLaunchActivity方法,在该方法中会创建Context,下面就可以创建的相应代码:

frameworks/base/core/java/android/app/ActivityThread.java

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ......
    //创建Activity的ContextImpl
    ContextImpl appContext = createBaseContextForActivity(r);
    Activity activity = null;
    try {
        java.lang.ClassLoader cl = appContext.getClassLoader();
        //创建Activity
        activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);
        ......
    } catch (Exception e) {
        ......
    }
    try {
        ......
        if (activity != null) {
            ......
            //将activity设置给ContextImpl的成员变量mOuterContext
            appContext.setOuterContext(activity);
            //将ContextImpl对象传入
            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);

            ......
            if (r.isPersistable()) {
                //该方法最终会调用Activity的onCreate方法
                mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
            } else {
                mInstrumentation.callActivityOnCreate(activity, r.state);
            }
            ......
        }
        r.paused = true;

        mActivities.put(r.token, r);

    } catch (SuperNotCalledException e) {
      ......
    }
    return activity;
}

在该方法中与ActivityContext相关的逻辑处理主要做了一下几件事:

  • 1.创建与Activity相关的ContextImpl对象
  • 2.创建Activity,然后赋值给ContextImpl的成员变量mOuterContext,这样ContextImpl就可以访问Activity的变量和方法。
  • 3.调用Activityattach方法,绑定ContextImpl对象

createBaseContextForActivity方法会调用ContextImpl的静态方法createActivityContext来创建ContextImpl

下面重点看一下Activityattach方法

frameworks/base/core/java/android/app/Activity.java

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);

    mFragments.attachHost(null /*parent*/);
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mWindow.setWindowControllerCallback(this);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    mWindow.getLayoutInflater().setPrivateFactory(this);
    ......
    mWindow.setWindowManager(
            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
            mToken, mComponent.flattenToString(),
            (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
    if (mParent != null) {
        mWindow.setContainer(mParent.getWindow());
    }
    mWindowManager = mWindow.getWindowManager();
    mCurrentConfig = config;
    mWindow.setColorMode(info.colorMode);
}

在该方法中会调用Activity父类ContextThemeWrapperattachBaseContext方法,在这个方法中会有调用它的父类ContextWrapperattachBaseContext方法,该方法的实现我们在上面Application Context的创建过程已分析,就是将一路传递过来的ContextImpl对象赋值给ContextWrapper的成员变量mBase

ServiceContext创建过程与Activity的类似,这里不再阐述。

总结

Application、Activity和Service的Context创建过程类似,都是先创建各自对应的ContextImpl对象,然后调用attach方法,该方法最终调用ContextWrapperattachBaseContext方法,将创建的ContextImpl对象赋值给ContextWrapper的成员变量mBase。我们在开发中在ApplicationActivityService中调用Context的方法,实际都是调用mBase所指向的ContextImpl对象的方法,再结合上面给出的Context相关类的示图,就可以很清晰的理解它们之间的关系了。

实战

推荐阅读更多精彩内容