详解 Context

826 阅读5分钟

Android开发经常会用到Context,例如:context.getResource(),context.startActivity(),context.startService()等等。那么Context到底是什么呢,为什么我们的Activity中、Service中、BroadcastReceiver中还有Application中都有他的身影。这个被我们整天叫做“上下文”的对象到底在App中起到了什么作用呢?这些就是我们要分析理解的重点。

Context 的定义

Context 是一个关于应用环境的抽象类,它的实现由android系统提供。用来访问一些应用内资源和类,也可以调用系统服务开启Activity,Service或发送广播等。

Context 继承关系

Context本身是一个 抽象类,其直接继承者ContextImplContextWrapper两大类。图中表示了组件与Context之间的继承关系。 Context族系继承关系

从名字可以看出,ContextWrapper 是一个Context的包装类,而ContextImpl似乎是真正实现了Context核心功能的实现类。 带着这个思路我们查看一下ContextImpl的源码:

/**
 * Common implementation of Context API, which provides the base
 * context object for Activity and other application components.
 * 
 */
class ContextImpl extends Context {
    。。。此处省略2000多行代码。。。
}

其实在这个问题上,我们需要关注的只有类注释就够了,翻译过来是:这是通用的Context 方法的实现类,为Activity等组件提供base context的实例。再来对比看下ContextWrapper的源码:

/**
 * Proxying implementation of Context that simply delegates all of its calls to
 * another Context.  Can be subclassed to modify behavior without changing
 * the original Context.
 *
 */
public class ContextWrapper extends Context {
    Context mBase;

    public ContextWrapper(Context base) {
        mBase = base;
    }
    
    /**
     * Set the base context for this ContextWrapper.  All calls will then be
     * delegated to the base context.  Throws
     * IllegalStateException if a base context has already been set.
     * 
     * @param base The new base context for this wrapper.
     */
    protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        mBase = base;
    }
    。。。此处省略1000多行代码。。。
}

同样提供了类注释:这是一个Context的代理类,它代理了所有 Context抽象类 的API。该代理类能够被再继承,以改变Context 的行为,同时又不去改变被代理Context 的原有行为。 我们深入代码(代码这里就省略了,全都是调用的super)则会发现,确实ContextWrapper除了上面截取出来的一段代码外,其余方法全部都是调用了super。这里坐实了它的代理模式,而它构造方法中传入的mBase,也正是ContextImpl实例。我们可以拿Activity举例来看mBase的赋值时机。

在Activity的attach()方法中调用了ContextWrapper的attachBaseContext(),并传入了一个Context的实例。分析过Activity启动过程之后,我们会知道Activity.attach()会在ActivityThread.performLaunchActivity()的时候调用到。

 private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ActivityInfo aInfo = r.activityInfo;
        。。。省略。。。
        ContextImpl appContext = createBaseContextForActivity(r);//创建Activity使用的ContextImpl实例
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(           //顺便能够发现,activity的实例化是谁干的。
                    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,//为新的activity实例进行初始化attach
                        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()) {
			mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
		} else {
			mInstrumentation.callActivityOnCreate(activity, r.state);
		}
        。。。省略。。。
                

我们省略了大段其他逻辑来凸显出activity的实例化过程和attach的调用过程,发现performLaunchActivity()做了四件事:

  1. 为Activity生成ContextImpl实例,叫做appContext。
  2. 从appContext中获取ClassLoader,实例化Activity。
  3. 获取Application对象。
  4. 调用activity.attach(),把appContext,application对象统统关联进去。
  5. 调用activity.onCreate(),执行我们熟知的生命周期。

传入的appContext来自于createBaseContextForActivity()。继续跟进,能够在ContextImpl类中找到createActivityContext():

static ContextImpl createActivityContext(ActivityThread mainThread,
            LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,
            Configuration overrideConfiguration) {
        if (packageInfo == null) throw new IllegalArgumentException("packageInfo");

        String[] splitDirs = packageInfo.getSplitResDirs();
        ClassLoader classLoader = packageInfo.getClassLoader();

        if (packageInfo.getApplicationInfo().requestsIsolatedSplitLoading()) {
            Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "SplitDependencies");
            try {
                classLoader = packageInfo.getSplitClassLoader(activityInfo.splitName);
                splitDirs = packageInfo.getSplitPaths(activityInfo.splitName);
            } catch (NameNotFoundException e) {
                // Nothing above us can handle a NameNotFoundException, better crash.
                throw new RuntimeException(e);
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
            }
        }
	  // ContextImpl 的构造函数是private,因此只能使用ContextImpl提供 的静态方法进行初始化获取。	  
      // ContextImpl实例持有mainThread、packageInfo、classLoader,如果是给Activity用,则还会有非空的activityToken。(某些Window、Dialog为何只能使用Activity或Activity的Context去加载的原因)
        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName,
                activityToken, null, 0, classLoader);

        // Clamp display ID to DEFAULT_DISPLAY if it is INVALID_DISPLAY.
        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();

        // Create the base resources for which all configuration contexts for this Activity
        // will be rebased upon.
        context.setResources(resourcesManager.createBaseActivityResources(activityToken,
                packageInfo.getResDir(),
                splitDirs,
                packageInfo.getOverlayDirs(),
                packageInfo.getApplicationInfo().sharedLibraryFiles,
                displayId,
                overrideConfiguration,
                compatInfo,
                classLoader));
        context.mDisplay = resourcesManager.getAdjustedDisplay(displayId,
                context.getResources());
        return context;
    }

最终我们弄清楚了,Android组件中的context和组件之间的关系,其实就是在使用不同的组件过程中(比如Activity),组件通过静态代理的方式调用到ContextImpl的核心功能,同时针对自身的特性(比如Activity需要显示界面,定制主题等)进行一些行为变更。换句话说,与其将Activity叫做Context,不如说Activity代理了Context。Service也类似。

有时候面试官可能会问这么一个问题:一个App中有多少个Context? 根据图片中的继承关系可以知道,Activity、Service、Application都是Context的子类。因此宏观上来看,有多少Activity+Service+Application,就会有多少个Context。要注意的是,Application并不是每个App只有一个,而是App开了几个进程,就会有几个Application实例。这个答案精确的来说其实并不准确,因为我们发现,Context的另一个子类:ContextImpl并没有被包含进去。而组件中所持有的mBase,就正好是ContextImpl的实例。mBase事关启动流程,这里就不再展开叙述。

思考:

  1. Activity中,this作为 Context 和 getBaseContext()获取的Context有什么区别?

    this 是Activity对象本身,虽然也是Context的子类,但它的直接父类是ContextThemeWrapper,而不是Context。对照上图继承关系会发现,ContextThemeWrapper继承于ContextWrapper,源码中继承了绝大部分父类的行为,并增加了显示相关的API,比如setTheme()设置主题。而getBaseContext获取到的则是mBase变量在Activity实例化时传入的context实例。

  2. Activity中能够获取到Application实例,也能够获取到 ContextImpl实例,那么他们创建的时机是什么?

    Activity中的getApplication()和getBaseContext()方法所获取的对象,均是在Activity创建时被关联进来的。ActivityThread在执行performLaunchActivity()时,获取了application和context,在调用Activity.attach()的时候设置进去。

  3. Service中的ContextImpl实例创建时机是什么? 类似于Activity,Service中也存在着attach()的方法,它的调用时机是ActivityThread.handleCreateService()。这其中干了5件事儿:

    • 获取ClassLoader,实例化Service。
    • 通过ContextImpl.createAppContext()静态方法,实例化ContextImpl。
    • 通过packageInfo.makeApplication()获取Application实例。
    • 调用service.attach()关联上述context和application实例。
    • 调用service.onCreate()。