Android 中的 Context 到底是什么 ?

什么是 Context ?

在Android平台上 , Context 是一个基本的概念,它在逻辑上表示一个运行期的“上下文”。在Android平台上,应用里的每个重要UI界面都用一个小型上下文来封装,
而每个重要的对外服务也都用一个小型上下文封装。这些小型上下文都容身到一个Android大平台上, 并由Android统一调度管理, 形成一个统一的整体。

Context的行为

Context体现到代码上来说,是个抽象类,其主要表达的行为列表如下:

Context行为分类         常用函数

使用系统提供的服务        getPackageManager()
                       getSystemService()......

基本功能                startActivity()、sendBroadcast()
                       registerReceiver()、startService()
                       bindService()、getContentResolver()......
                       [内部基本上是和AMS打交道]

访问资源                 getAssets()、getResources()、
                       getString()、getColor()、getClassLoader()......
                       getApplicationContext()......

和信息存储相关             getSharedPreferences()、openFileInput()、
                        openFileOutput()、deleteFile()、openOrCreateDatabase()、
                        deleteDatabase()......

既然是抽象类,最终就总得需要实际的派生类才行。在Android上,我们可以画出如下的 Context 继承示意图
在这里插入图片描述

我们可以琢磨一下这张图,很明显,在Android平台上,Activity 和 Service 在本质上都是个 Context, 而代表应用程序的Application对象,也是个Context,
这个对象对应的就是 AndroidManifest.xml 里的部分。因为上下文访问应用资源或系统服务的动作是一样的,所以这部分动作被统一封装进一个ContextImpl辅助类里。
Activity、Service、Application内部都含有自己的ContextImpl,每当自己需要访问应用资源或系统服务时,无非是把请求委托给内部的ContextImpl而已

ContextWrapper

上图中还有个ContextWrapper,该类是用于表示Context的包装类,它在做和上下文相关的动作时,基本上都是委托给内部mBase域记录的Context(即ContextIml)去做的。
如果我们希望子类化上下文的某些行为,可以有针对性地重写ContextWrapper的一些成员函数。

ContextWrapper的代码截选如下:

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

public class ContextWrapper extends Context {
    Context mBase;

    public ContextWrapper(Context base) {
        mBase = base;
    }

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

    public Context getBaseContext() {
        return mBase;
    }

代码中的mBase是它的核心。

ContextWrapper只是简单封装了Context,它重写了Context所有成员函数,比如:

    @Override
    public AssetManager getAssets() {
        return mBase.getAssets();
    }

    @Override
    public Resources getResources() {
        return mBase.getResources();
    }

ContextImpl

前文我们已经说过,因为上下文访问应用资源或系统服务的动作是一样的,所以这部分动作被统一封装进一个 ContextImpl 辅助类里,现在我们就来看看这个类。

ContextImpl的代码截选如下:

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

class ContextImpl extends Context {
    private final static String TAG = "ContextImpl";
    private final static boolean DEBUG = false;

    @GuardedBy("ContextImpl.class")
private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> 
sSharedPrefsCache;
    @GuardedBy("ContextImpl.class")
    private ArrayMap<String, File> mSharedPrefsPaths;

    final ActivityThread mMainThread;  // 依靠该成员和大系统联系
    final LoadedApk mPackageInfo;

    private final IBinder mActivityToken;
    private final UserHandle mUser;
    private final ApplicationContentResolver mContentResolver;

    private final String mBasePackageName;
    private final String mOpPackageName;

    private final @NonNull ResourcesManager mResourcesManager;
    private final @NonNull Resources mResources;  // 指明自己在用的资源
    private @Nullable Display mDisplay; 
    private final int mFlags;

    private Context mOuterContext;   // 指向其寄身并提供服务的上下文。

    private int mThemeResource = 0;
    private Resources.Theme mTheme = null;
    private PackageManager mPackageManager;
    private Context mReceiverRestrictedContext = null;

很明显,作为一个上下文的核心部件,ContextImpl有责任和更大的系统进行通信(我们可以把Android平台理解为一个大系统),所以它会有一个mMainThread成员。
就以启动activity动作来说吧,最后会走到ContextImpl的startActivity(),而这个函数内部大体上是进一步调用 mMainThread.getInstrumentation().execStartActivity(),
从而将语义发送给Android系统。

ContextImpl里的另一个重要方面是关于资源的访问。这就涉及到资源从哪里来。简单地说,当一个APK被加载起来时,系统会创建一个对应的LoadedApk对象,
并通过解码模块将APK里的资源部分加载进LoadedApk。每当我们为一个上下文创建对应的ContextImpl对象时,就会从LoadedApk里获取正确的Resources对象,
并记入ContextImpl的mResources成员变量,以便以后使用。

什么时候创建Context实例

创建 Application 对象的时机

每个应用程序在第一次启动时,都会首先创建Application对象。如果对应用程序启动一个Activity(startActivity)流程比较清楚的话,创建Application 的时机在创建handleBindApplication()方法中,该函数位于 ActivityThread.java类中 ,如下:

 //创建Application时同时创建的ContextIml实例
private final void handleBindApplication(AppBindData data){
    ...
    ///创建Application对象
    Application app = data.info.makeApplication(data.restrictedBackupMode, null);
    ...
}
 
public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) {
    ...
    try {
        java.lang.ClassLoader cl = getClassLoader();
        ContextImpl appContext = new ContextImpl();    //创建一个ContextImpl对象实例
        appContext.init(this, null, mActivityThread);  //初始化该ContextIml实例的相关属性
        ///新建一个Application对象 
        app = mActivityThread.mInstrumentation.newApplication(
                cl, appClass, appContext);
       appContext.setOuterContext(app);  //将该Application实例传递给该ContextImpl实例         
    } 
    ...
}

创建 Activity 对象的时机

通过startActivity()或startActivityForResult()请求启动一个Activity时,如果系统检测需要新建一个Activity对象时,就会回调handleLaunchActivity()方法,该方法继而调用 performLaunchActivity()方法,去创建一个Activity实例,并且回调 onCreate(),onStart() 方法等, 函数都位于 ActivityThread.java类 ,如下:

//创建一个Activity实例时同时创建ContextIml实例
private final void handleLaunchActivity(ActivityRecord r, Intent customIntent) {
    ...
    Activity a = performLaunchActivity(r, customIntent);  //启动一个Activity
}
private final Activity performLaunchActivity(ActivityRecord r, Intent customIntent) {
    ...
    Activity activity = null;
    try {
        //创建一个Activity对象实例
        java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
        activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
    }
    if (activity != null) {
        ContextImpl appContext = new ContextImpl();      //创建一个Activity实例
        appContext.init(r.packageInfo, r.token, this);   //初始化该ContextIml实例的相关属性
        appContext.setOuterContext(activity);            //将该Activity信息传递给该ContextImpl实例
        ...
    }
    ...    
}

创建 Service 对象的时机

通过startService或者bindService时,如果系统检测到需要新创建一个Service实例,就会回调handleCreateService()方法,完成相关数据操作。handleCreateService()函数位于 ActivityThread.java类,如下:

//创建一个Service实例时同时创建ContextIml实例
private final void handleCreateService(CreateServiceData data){
    ...
    //创建一个Service实例
    Service service = null;
    try {
        java.lang.ClassLoader cl = packageInfo.getClassLoader();
        service = (Service) cl.loadClass(data.info.name).newInstance();
    } catch (Exception e) {
    }
    ...
    ContextImpl context = new ContextImpl(); //创建一个ContextImpl对象实例
    context.init(packageInfo, null, this);   //初始化该ContextIml实例的相关属性
    //获得我们之前创建的Application对象信息
    Application app = packageInfo.makeApplication(false, mInstrumentation);
    //将该Service信息传递给该ContextImpl实例
    context.setOuterContext(service);
    ...
}

需要强调一点的是, ContextImp 的分析可知,其方法的大多数操作都是直接调用其属性mPackageInfo(该属性类型为PackageInfo)的相关方法而来。这说明 ContextImp 是一种轻量级类,而 PackageInfo 才是真正重量级的类。而一个 App 的所有 ContextIml 实例,都对应同一个 packageInfo 对象。

所有ContextIml实例,都对应同一个packageInfo对象。

Application中的 Context 和 Activity 中的 Context 的区别

在需要传递Context参数的时候,如果是在Activity中,我们可以传递this(这里的this指的是Activity.this,是当前Activity的上下文)或者Activity.this。这个时候如果我们传入getApplicationContext(),我们会发现这样也是可以用的。可是大家有没有想过传入Activity.this和传入getApplicationContext()的区别呢?首先Activity.this和getApplicationContext()返回的不是同一个对象,一个是当前Activity的实例,一个是项目的Application的实例,这两者的生命周期是不同的,它们各自的使用场景不同,this.getApplicationContext()取的是这个应用程序的Context,它的生命周期伴随应用程序的存在而存在;而Activity.this取的是当前Activity的Context,它的生命周期则只能存活于当前Activity,这两者的生命周期是不同的。getApplicationContext() 生命周期是整个应用,当应用程序摧毁的时候,它才会摧毁;Activity.this的context是属于当前Activity的,当前Activity摧毁的时候,它就摧毁。

Activity Context 和Application Context两者的使用范围存在着差异,具体如下图所示:
在这里插入图片描述

我们就只看Activity和Application,可以看到前三个操作不在 Application 中出现,也就是Show a Dialog、Start an Activity和Layout Inflation。开发的过程中,我们主要记住一点,凡是跟UI相关的,都用Activity做为Context来处理。

Context数量

在创建Activity、Service、Application时都会自动创建Context,它们各自维护着自己的上下文。在Android系统中Context类的继承结构里面我们讲到Context一共有Application、
Activity和Service三种类型,因此如果要统计一个app中Context数量,我们可以这样来表示:

Context数量 = Activity数量 + Service数量 + 1

这里要解释一下,上面的1表示Application数量。一个应用程序中可以有多个Activity和多个Service,但只有一个Application。可能有人会说一个应用程序里面可以有多个Application啊,
我的理解是:一个应用程序里面可以有多个 Application,可是在配置文件AndroidManifest.xml中只能注册一个,只有注册的这个Application才是真正的Application,
才会调用到全部的生命周期,所以Application的数量是1。

参考

认识一下Android里的Context
Android Application中的Context和Activity中的Context的异同

展开阅读全文

没有更多推荐了,返回首页