Android之Context底层原理

Context的中文翻译为:语境; 上下文; 背景; 环境,在开发中我们经常说称之为“上下文”。从Android系统的角度来理解:Context是一个场景,代表与操作系统的交互的一种过程。Context在加载资源、启动Activity、获取系统服务、创建View等操作都要参与  。从程序的角度上来理解:Context是个抽象类,而Activity、Service、Application等都是该类的一个实现。

  • 获取AssetManager:getAssets();
  • 获取Resources:getResources();
  • 获取PackageManager:getPackageManager();
  • 获取ContentResolver:getContentResolver();
  • 获取主线程Looper:getMainLooper();
  • 获取Application的Context:getApplicationContext();
  • 获取资源文件:getText,getString,getColor,getDrawable,getColorStateList;
  • 设置主题,获取主题资源id:setTheme,getThemeResId;
  • 获取样式属性TypedArray:obtainStyledAttributes();
  • 获取类加载器ClassLoader:getClassLoader();
  • 获取应用信息对象ApplicationInfo:getApplicationInfo();
  • 获取SharedPreferences:getSharedPreferences();
  • 打开文件FileInputStream:openFileInput();
  • 删除文件:deleteFile();
  • 获取文件File:getFileStreamPath();
  • 打开或者创建数据库:openOrCreateDatabase();
  • 移除或者删除数据库:moveDatabaseFrom(),deleteDatabase();
  • 启动Activity:startActivity(),startActivityAsUser(),startActivityForResult(),startActivities();
  • 注册、发送、注销广播:registerReceiver(),sendBroadcast(),sendOrderedBroadcast(),unregisterReceiver();
  • 启动、绑定、解除绑定、停止服务:startService(),bindService(),unbindService(),stopService();
  • 获取系统服务:getSystemService();
  • 检查权限(Android 6.0以上):checkPermission();
  • 根据应用名创建Context:createPackageContext();
  • 根据应用信息创建Context:createApplicationContext();
  • 获取显示信息对象Display:getDisplay();

Android之Context底层原理

1.ContextImpl、ContextWrapper与Context的关系

Context是一个抽象类,ContextImpl和ContextWrapper都继承了Context,也就是都实现了Context的抽象方法,但是,从代码中我们看到ContextImpl是Context抽象方法的详细实现类,而ContextWrapper是调用了mBase对应的方法,而mBase是Context,从代码跟踪看mBase其实就是ContextImpl,因此ContextWrapper最终是调用ContextImpl中的实现方法。也就是说我们调用的Context中的任何方法都是在ContextImpl中处理的,因此我们在跟踪代码时只需要去ContextImpl中查看对应方法处理就好了。上面只是介绍,下面我们根据具体代码来分析一下到底怎么实现的。

从ContextWrapper代码中我们可以看到(不贴全部代码了),只有构造函数、attachBaseContext方法以及getBaseContext方法不是复写方法,其他方方法均为复写方法:

【ContextWrapper.java】

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

    /**
     * @return the base context as set by the constructor or setBaseContext
     */
    public Context getBaseContext() {
        return mBase;
    }

我们可以看到只有构造函数和attachBaseContext方法传入了mBase,那么从attachBaseContext方法中我们看到如果mBase存在又调用了该方法就会抛出异常,因此我们知道如果调用了该方法,那么构造函数不能传入这个值,我们看一下哪些地方调用了这个attachBaseContext方法,由代码可以看到Application、activity和service均调用了这个方法,首先我们来看Application中的代码:

【Application.java】

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

Application中的attach方法中调用了attachBaseContext方法,参数context也是通过attach方法传入的,那么我们再跟踪这个attach方法:

是在Instrumentation类中调用的:

【Instrumentation.java】

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

上面方法调用地方是:

【Instrumentation.java】

    public Application newApplication(ClassLoader cl, String className, Context context)
            throws InstantiationException, IllegalAccessException, 
            ClassNotFoundException {
        return newApplication(cl.loadClass(className), context);
    }

从代码可以看到是在new Application时调用的,那么我们接着看哪里调用了这个方法:

【LoadedApk.java】

public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation) {
        ...

        try {
            ...
            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);
            appContext.setOuterContext(app);
        } catch (Exception e) {
            ...
        }
        ...
        return app;
    }

上面方法是在LoadedApk类中调用的,我们先不分析这个类,后续我们会详细讲这个过程,我们先分析上面这段代码,我们看到这里面通过调用ContextImpl.createAppContext方法来创建ContextImpl,然后将参数传入newApplication方法,因此我们看到上面的mBase就是ContextImpl,那么还有Activity和Service.我们先分析Service,因为从关系图可以看到Service和Application都是直接继承ContextWrapper,而Activity则是继承ContextThemeWrapper,ContextThemeWrapper继承ContextWrapper。

【Service.java】

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

attachBaseContext方法是在Service中的attach方法中调用的,接着看attach方法的调用:

【ActivityThread.java】

private void handleCreateService(CreateServiceData data) {
        ...
            ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
            context.setOuterContext(service);

            Application app = packageInfo.makeApplication(false, mInstrumentation);
            service.attach(context, this, data.info.name, data.token, app,
                    ActivityManagerNative.getDefault());
            service.onCreate();
         ...
    }

在这里我们看到传入的context就是ContextImpl,从而得到验证,下面我们还看到service.onCreate方法,我们看到了先调用attach方法然后调用onCreate方法。

最后我们看一下Activity,从上面关系图我们看到,Activity不是直接继承ContextWrapper,而是继承的ContextThemeWrapper,ContextThemeWrapper继承ContextWrapper。从名字我们可以看到ContextThemeWrapper包含主题的信息,其实不难理解,四大组件只有Activity是带界面的,其他都是没有界面的,因此Activity需要主题信息来显示不同的界面效果。在ContextThemeWrapper中我们看到复写了attachBaseContext方法,方法中只有一行代码就是调用父类的attachBaseContext方法。如下所示:

【ContextThemeWrapper.java】

    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(newBase);
    }

在Activity中只有一个方法中调用了该方法,看代码:

【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) {
        attachBaseContext(context);

        ...
    }

我们接着追踪attach方法,看代码:

【ActivitThread.java】

 private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
       ...
        
            if (activity != null) {
                Context appContext = createBaseContextForActivity(r, 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);

                ...
            }
            ...
        return activity;
    }

方法performLaunchActivity其实是启动Activity的方法,这里我们暂时不讲,后续我们会详细讲解,我们先理清楚Context,从上面代码我们可以看到此处传入的Context是通过createBaseContextForActivity方法创建的,那么我们看一下这个方法:

【ActivitThread.java】

private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) {
        ...

        ContextImpl appContext = ContextImpl.createActivityContext(
                this, r.packageInfo, r.token, displayId, r.overrideConfig);
        appContext.setOuterContext(activity);
        Context baseContext = appContext;

        ...
        return baseContext;
    }

从上面代码我肯可以清楚的看到baseContext是appContext赋值的,而appContext就是ContextImpl,因此Activity中的Context也是ContextImpl。