Android中的Context

framework

Posted by Cc1over on January 16, 2019

Context

前言

说到Context,它其实是我们熟悉又陌生的小伙伴了,一开始我们都感知不到Context的存在,而是被迫使用Context的,比如自定义View构造函数就要传入Context,发送广播的时候,又必须使用Context,于是乎,我们通常会设计一个成员mContext来保存外部传入的Context,但是这样有造成一些问题:

  • 好不容易想实现控件的代码解耦,为什么要把Context传来传去呢?
  • 为什么不能像在Activity中一样,直接调用sendBroadcast()就可以了呢?
  • 为什么通过Context可以调用很多Android的接口,譬如getString(), getColor(), startActivity()呢?

下面我们就来解决这些问题~

Context的设计思想

面向应用程序的设计

Android有意淡化进程的概念,在开发一个Android应用程序的时候,通常都不需要关心目标对象运行在哪个进程,只需要表面意图(Intent),比如打电话,查看图片,打开链接也不需要关心系统接口是在哪个进程实现的,只需要通过Context发起调用。对于一个Android应用,Context就像运行环境一样,一旦持有Context就会成为一个Android的公民,从而享有Android提供的一些服务

  • 获取应用资源,譬如:drawable、string、assets
  • 操作四大组件,譬如:启动界面、发送广播、绑定服务、打开数据库
  • 操作文件目录,譬如:获取/data/分区的数据目录、获取sdcard目录
  • 检查授予权限,譬如:应用向外提供服务时,可以判定申请者是否具备访问权限
  • 获取其他服务,有一些服务有专门的提供者,譬如:包管理服务、Activity管理服务、窗口管理服务

Context类族的设计

Context族的设计用到了装饰者模式,基于Context制定的接口,ContextImp负责实现接口的具体功能,对外提供使用时,ContextImpl需要被包装(Wrapper)一下,这就有了ContextWrapper这个装饰者,ContextWrapper的设计目的在于扩展功能,虽然Context已经提供了十分丰富的功能,但仍不能满足最终应用程序编程的需要,因此Android又扩展了一些修饰器,包括Application、Activity和Service

Application扩展了应用程序的生命周期,Activity扩展了界面显示的生命周期,Service扩展了后台服务的生命周期,它们在父类Context的基础上进行了不同维度的扩展,同时也仍可以将它们作为Context使用,这可以解释很多Applicaiton、Activity和Service的使用方式,但很多问题也随之而来

  • 为什么四大组件的另外两个BroadcastReceiver和ContentProvider不是Context的子类呢?
  • 为什么Applictaion、Activity和Service不直接继承ContextImp呢?

下面以ContextProvider的构造函数和BrocadcastReceiver.onReceive()方法为例

// ContentProvider
public ContentProvider(
        Context context,
        String readPermission,
        String writePermission,
        PathPermission[] pathPermissions) {
    mContext = context;
    mReadPermission = readPermission;
    mWritePermission = writePermission;
    mPathPermissions = pathPermissions;
}

// BroadcastReceiver
public abstract void onReceive(Context context, Intent intent);

ContentProvider和BroadcastReceiver都需要把Context作为参数传入,虽然它们不继承于Context,但它们都依赖于Context,换个角度看:它们就是修饰器,包装了Context。因为这两个组件在使用上与Activity和Service存在较大的区别,所以它们的实现方式存在较大差异

往深一步理解,装饰者模式的优势也体现出来了,比如Application、Activity和Service都可以作为BroacastReceiver的载体,只需要通过它们各自的Context去注册广播接收器就可以了,将BroadcastReceiver修饰在它们之,就可以形成新的功能扩展,而不是去实现一些可以接收广播的Applicaiton、Activity或Service类

小结

至此,Context的设计思想已经渗透了,从应用程序与系统通信的角度来说,它是应用进程与系统对话的一个接口;从使用的角度,更是可以将Context理解为应用进程的Android运行环境,想要什么资源,都可以向Context索取;而从实现的角度来说Context的类族运用了装饰者模式,Android最核心的四大组件都可以理解成装饰者,它们从不同的维度对Context进行了扩展

Context源码解析

上面也提及到了Application、Activity、Service都是ContextImp的装饰类,在这个源码解析的过程,笔者将忽略一些实现的细节,重点解决的是两个问题:

  • ComtextImp是如何被创建的?
  • ComtextImp是如何被装饰,然后成为我们耳熟能详的Activity、Service、Application的?

ContextImp是如何被创建的?

解决这个问题,首先明确一点,ContextImp有多少种?

  • SystemContext:系统进程SystemServer的Context
  • AppContext:应用进程的Context
  • ActivityContext:Activity的Context,只有ActivityContext跟界面显示相关,需要传入activityToken和有效的DisplayId
SystemContext的创建
public final class SystemServer {
    private void run() {
        ...
        createSystemContext(); 
        startBootstrapServices(); //开始启动服务
        ...
    }
}

private void createSystemContext() {
    ActivityThread activityThread = ActivityThread.systemMain(); 
    mSystemContext = activityThread.getSystemContext(); 
 mSystemContext.setTheme(android.R.style.Theme_DeviceDefault_Light_DarkActionBar);
}

以上是SystemContext的创建过程,这里就不去深究了,重点看一下的是后面两种Context:

AppContext的创建
// ContextImpl.createAppContext()
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
    if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
    return new ContextImpl(null, mainThread,
            packageInfo, null, null, 0, null, null, Display.INVALID_DISPLAY);
}

// ContextImpl.constructor()
private ContextImpl(ContextImpl container, ActivityThread mainThread,
        LoadedApk packageInfo, IBinder activityToken, UserHandle user, int flags,
        Display display, Configuration overrideConfiguration, int createDisplayWithId) {
    mOuterContext = this; // 外围包装器,暂时用
    ...
    mMainThread = mainThread; // 主线程
    mActivityToken = activityToken; // 关联到系统进程的ActivityRecord
    mFlags = flags;
    ...
    mPackageInfo = packageInfo; // LoadedApk对象
    mResourcesManager = ResourcesManager.getInstance();
    ...
    Resources resources = packageInfo.getResources(mainThread);
    ...
    mResources = resources; // 通过各种计算得到的资源
    ...
    mContentResolver = new ApplicationContentResolver(this, mainThread, user); // 访问ContentProvider的接口
}

上面这段代码片段是创建一个AppContext,主要展示的部分是初始化一些关键,日常开发涉及比较多的一些成员,特别注意的是Context中会初始化一个mContentResolver对象,所以,可以通过Context操作数据库

ActivityContext的创建
static ContextImpl createActivityContext(ActivityThread mainThread,
            LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,
            Configuration overrideConfiguration) {
    ...
        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName,
                activityToken, null, 0, classLoader);
        displayId = (displayId != Display.INVALID_DISPLAY) ? displayId : Display.DEFAULT_DISPLAY;
    ...    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;
    }

从上面的片段源码可以看出,其实ActivityContext的创建和AppContext的创建区别不大,只是配置参数不一样而已

小结

至此,ContextImp创建的关键部分已经总结得差不多了,接下来我们就来解决第二个问题:ContextImp是如何被装饰的?

ContextImp是如何被装饰

ContextImp的装饰过程,我们列举两个例子来说明,分别是Applictaion和Activity

Applictaion部分
public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) {
    ...
    if (forceDefaultAppClass || (appClass == null)) {
        appClass = "android.app.Application";
    }
    try {
        java.lang.ClassLoader cl = getClassLoader();
        ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
        app = mActivityThread.mInstrumentation.newApplication(
                cl, appClass, appContext);
        appContext.setOuterContext(app);
    } catch (Exception e) {...}
    ...
}
static public Application newApplication(Class<?> clazz, Context context)
        throws InstantiationException, IllegalAccessException, ClassNotFoundException {
    Application app = (Application)clazz.newInstance();
    app.attach(context);
    return app;
}

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

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

这是Application创建的片段代码,从上面的代码我们也可以看到,系统中其实是用反射机制去创建Application的,果应用程序没有继承实现Application,则默认使用android.app.Application这个包名进行反射。构建Application对象完成后,就通过attach()方法去绑定一个Context,这里的mLoadedApk其实是apk在内存中的数据结构,这一绑定就相当于在ContextWrapper中关联了一个ContextImpl,于是,这样一层修饰包装的关系就套上了

Activity部分
 if (activity != null) {
            // 3. 创建Activity的Context
            Context appContext = createBaseContextForActivity(r, activity);
            CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
            Configuration config = new Configuration(mCompatConfiguration);
            ...
            // 4. 将Context包装进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);
            ...

上面的片段代码是Activity启动过程中的performLaunchActivity中的其中一个片段,我们可以看到,创建完成ActivityContext之后其实它的装饰方式和appliction是类似的,而这个createBaseContextForActivity方法最终只是调用了上面createActivityContext()而已,这里就不再重复了

小结

可见,Activity和Application的Context构建过程极为相似,这也是符合逻辑的,因为它们本质上就是Context,只不过功能维度不同。其实Service的Context构建过程也很相似。无非都是构建ContextImpl对象,然后在外面附上一层不同的装饰而已

Context的注意事项

从前文中可以看出,Android中有好几种不同的Context,在一个Activity中,就可以获取到以下几种Context:

  • getApplication()
  • getApplicationContext()
  • getBaseContext()
  • Activity.this
// 插入代码
Log.i(TAG, "Application: " + getApplication());
Log.i(TAG, "ApplicationContext: " + getApplicationContext());
Log.i(TAG, "Activity: " + this);
Log.i(TAG, "ActivityContext:" + this);
Log.i(TAG, "Application BaseContext: " + getApplication().getBaseContext());
Log.i(TAG, "Activity BaseContext: " + getBaseContext());

// 运行结果
 E/MainActivity: Application: android.app.Application@4aadfda
 E/MainActivity: ApplicationContext: android.app.Application@4aadfda
 E/MainActivity: Activity: com.example.cc1over.cc1over.MainActivity@664ff5d
 E/MainActivity: ActivityContext:com.example.cc1over.cc1over.MainActivity@664ff5d
 E/MainActivity: Application BaseContext: android.app.ContextImpl@991ec0b
 E/MainActivity: Activity BaseContext: android.app.ContextImpl@48f0ae8

可以看到,有以下几点不同:

  • getApplication()和getApplicationContext()返回的是同一个对象,虽然同一块内存区域,但对象的类型不同:前者是Application,后者是Context。Java是强类型的语言,Application到Context相当于向上转型,会丢失掉一些接口的访问入口。
  • 同理,Activity和Activity Context也是同一个对象,不同的类型。
  • Application和Activity的Base Context都是ContextImpl对象,正是这个Context真正的实现类,被外围的修饰器包装了一下,才形成不同功能的类。