美文网首页
深入理解 Android 中的各种 Context

深入理解 Android 中的各种 Context

作者: StevenChou | 来源:发表于2018-12-23 23:38 被阅读77次

    前言

    网上关于 Context 的文章也已经有不少了,比如值得参考的有:
    Android Context完全解析,你所不知道的Context的各种细节
    Android Context 到底是什么?

    但看了一下,发现还有值得讨论的地方,比如这个等式:

    Context个数 = Service 个数 + Activity 个数 + 1

    老实说,我不明白这个等式有什么意义,而且还是错的。首先多进程情况下,Application 对象就不止一个;其次,Activity、Service、Application 继承自 ContextWrapper,它们自己就是一个 Context,里面又有一个 Base Context;最后,还有各种 outer context、display context 什么的,这部分没深入研究过,但 Context 的数量绝对大于上述等式的两倍了。

    上面这部分算一个讨论,下面正式进入正题。

    Context 家族

    Context 本身是一个抽象类,主要实现类为 ContextImpl,另外有子类 ContextWrapper 和 ContextThemeWrapper,这两个子类都是 Context 的代理类,主要区别是 ContextThemeWrapper 有自己的主题资源。它们继承关系如下:

    Context 家族

    Context 有什么用?

    如果要弄清楚 “某个类有什么用” 这样的问题,其实很简单,看一下它提供了什么接口就知道了,下面列举一些主要的:

    /**
    * Interface to global information about an application environment.  This is
    * an abstract class whose implementation is provided by
    * the Android system.  It
    * allows access to application-specific resources and classes, as well as
    * up-calls for application-level operations such as launching activities,
    * broadcasting and receiving intents, etc.
    */
    public abstract class Context {
        
        // 四大组件相关
        public abstract void startActivity(@RequiresPermission Intent intent);
        public abstract void sendBroadcast(@RequiresPermission Intent intent);
        public abstract Intent registerReceiver(@Nullable BroadcastReceiver receiver,
                                                IntentFilter filter);
        public abstract void unregisterReceiver(BroadcastReceiver receiver);
        public abstract ComponentName startService(Intent service);
        public abstract boolean stopService(Intent service);
        public abstract boolean bindService(@RequiresPermission Intent service,
                @NonNull ServiceConnection conn, @BindServiceFlags int flags);
        public abstract void unbindService(@NonNull ServiceConnection conn);
        public abstract ContentResolver getContentResolver();
        
        // 获取系统/应用资源
        public abstract AssetManager getAssets();
        public abstract Resources getResources();
        public abstract PackageManager getPackageManager();
        public abstract Context getApplicationContext();
        public abstract ClassLoader getClassLoader();
        public final @Nullable <T> T getSystemService(@NonNull Class<T> serviceClass) { ... }
        
        public final String getString(@StringRes int resId) { ... }
        public final int getColor(@ColorRes int id) { ... }
        public final Drawable getDrawable(@DrawableRes int id) { ... }
        public abstract Resources.Theme getTheme();
        public abstract void setTheme(@StyleRes int resid);
        public final TypedArray obtainStyledAttributes(@StyleableRes int[] attrs) { ... }
        
        // 获取应用相关信息
        public abstract ApplicationInfo getApplicationInfo();
        public abstract String getPackageName();
        public abstract Looper getMainLooper();
        public abstract int checkPermission(@NonNull String permission, int pid, int uid);
        
        // 文件相关
        public abstract File getSharedPreferencesPath(String name);
        public abstract File getDataDir();
        public abstract boolean deleteFile(String name);
        public abstract File getExternalFilesDir(@Nullable String type);
        public abstract File getCacheDir();
        ...
        public abstract SharedPreferences getSharedPreferences(String name, @PreferencesMode int mode);
        public abstract boolean deleteSharedPreferences(String name);
        
        // 数据库相关
        public abstract SQLiteDatabase openOrCreateDatabase(...);
        public abstract boolean deleteDatabase(String name);
        public abstract File getDatabasePath(String name);
        ...
        
        // 其它
        public void registerComponentCallbacks(ComponentCallbacks callback) { ... }
        public void unregisterComponentCallbacks(ComponentCallbacks callback) { ... }
        ...
    }
    
    public interface ComponentCallbacks {
        void onConfigurationChanged(Configuration newConfig);
        void onLowMemory();
    }
    

    结合注释,可以发现,Context 就相当于 Application 的大管家,主要负责:

    1. 四大组件的交互,包括启动 Activity、Broadcast、Service,获取 ContentResolver 等
    2. 获取系统/应用资源,包括 AssetManager、PackageManager、Resources、System Service 以及 color、string、drawable 等
    3. 文件,包括获取缓存文件夹、删除文件、SharedPreference 相关等
    4. 数据库(SQLite)相关,包括打开数据库、删除数据库、获取数据库路径等
    5. 其它辅助功能,比如设置 ComponentCallbacks,即监听配置信息改变、内存不足等事件的发生

    ContextImpl 、ContextWrapper、ContextThemeWrapper 有什么区别?

    ContextWrapper

    先看 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;
        }
        
        protected void attachBaseContext(Context base) {
            if (mBase != null) {
                throw new IllegalStateException("Base context already set");
            }
            mBase = base;
        }
        
        // 这就是经常让人产生疑惑的  Base Context 了
        public Context getBaseContext() {
            return mBase;
        }
    
        // 下面这些方法全都直接通过 mBase 完成
        @Override
        public AssetManager getAssets() {
            return mBase.getAssets();
        }
    
        @Override
        public Resources getResources() {
            return mBase.getResources();
        }
    
        @Override
        public PackageManager getPackageManager() {
            return mBase.getPackageManager();
        }
        
        ...
        
    }
    

    可以看到,ContextWrapper 实际上就是 Context 的代理类而已,所有的操作都是通过内部成员 mBase 完成的,另外,Activity、Service 的 getBaseContext 返回的就是这个 mBase。

    ContextThemeWrapper

    接着看 ContextThemeWrapper,这个类的代码并不多,主要看 Resource 和 Theme 相关的:

    /**
    * A context wrapper that allows you to modify or replace the theme of the
    * wrapped context.
    */
    public class ContextThemeWrapper extends ContextWrapper {
        private int mThemeResource;
        private Resources.Theme mTheme;
        private LayoutInflater mInflater;
        private Configuration mOverrideConfiguration;
        private Resources mResources;
    
        public ContextThemeWrapper() {
            super(null);
        }
    
        public ContextThemeWrapper(Context base, @StyleRes int themeResId) {
            super(base);
            mThemeResource = themeResId;
        }
    
        public ContextThemeWrapper(Context base, Resources.Theme theme) {
            super(base);
            mTheme = theme;
        }
    
        @Override
        protected void attachBaseContext(Context newBase) {
            super.attachBaseContext(newBase);
        }
    
        // 在 Recource 初始化之前,传入配置信息
        public void applyOverrideConfiguration(Configuration overrideConfiguration) {
            if (mResources != null) {
                throw new IllegalStateException(...);
            }
            if (mOverrideConfiguration != null) {
                throw new IllegalStateException(...);
            }
            mOverrideConfiguration = new Configuration(overrideConfiguration);
        }
    
        public Configuration getOverrideConfiguration() {
            return mOverrideConfiguration;
        }
    
        // 没有重写 setResource,即 setResource 行为和父类一样
        @Override
        public Resources getResources() {
            return getResourcesInternal();
        }
    
        private Resources getResourcesInternal() {
            if (mResources == null) {
                if (mOverrideConfiguration == null) {
                    mResources = super.getResources();
                } else {
                    // 根据配置信息初始化 Resource
                    // 注意,这里创建了另一个和 Base Context 不同的 Resource
                    final Context resContext = createConfigurationContext(mOverrideConfiguration);
                    mResources = resContext.getResources();
                }
            }
            return mResources;
        }
    
        @Override
        public void setTheme(int resid) {
            if (mThemeResource != resid) {
                mThemeResource = resid;
                initializeTheme();
            }
        }
        
        private void initializeTheme() {
            final boolean first = mTheme == null;
            if (first) {
                // 根据 Resource 获取 Theme
                mTheme = getResources().newTheme();
                // 复制内容
                final Resources.Theme theme = getBaseContext().getTheme();
                if (theme != null) {
                    mTheme.setTo(theme);
                }
            }
            onApplyThemeResource(mTheme, mThemeResource, first);
        }
    
        protected void onApplyThemeResource(Resources.Theme theme, int resId, boolean first) {
            theme.applyStyle(resId, true);
        }
    
        @Override
        public Resources.Theme getTheme() {
            // 只会初始化一次
            if (mTheme != null) {
                return mTheme;
            }
    
            mThemeResource = Resources.selectDefaultTheme(mThemeResource,
                    getApplicationInfo().targetSdkVersion);
            initializeTheme();
    
            return mTheme;
        }
    
        ...
    
    }
    

    结合注释及源码,可以发现,相比 ContextWrapper,ContextThemeWrapper 有自己的另外 Resource 以及 Theme 成员,并且可以传入配置信息以初始化自己的 Resource 及 Theme。即 Resource 以及 Theme 相关的行为不再是直接调用 mBase 的方法了,也就说,ContextThemeWrapper 和它的 mBase 成员在 Resource 以及 Theme 相关的行为上是不同的。

    ContextImpl

    下面看一下 ContextImpl 有关 Theme 以及 Resource 的部分,以分析它和 ContextThemeWrapper 的区别:

    /**
    * Common implementation of Context API, which provides the base
    * context object for Activity and other application components.
    */
    class ContextImpl extends Context {
    
        private int mThemeResource = 0;
        private Resources.Theme mTheme = null;
        private @NonNull Resources mResources;
        
        // 用于创建 Activity Context
        static ContextImpl createActivityContext(...) {
            ContextImpl context = new ContextImpl(...);
            context.setResources(resourcesManager.createBaseActivityResources(...));
            return context;
        }
        
        // 用于创建 Application Context、Service Context
        static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
            ContextImpl context = new ContextImpl(...);
            context.setResources(packageInfo.getResources());
            return context;
        }
        
        private static Resources createResources(...) {
            return ResourcesManager.getInstance().getResources(...);
        }
    
        // ContextThemeWrapper 没有重写父类的 setResources
        // 因此会调用 mBase 的 setResources,即和 ContextImpl 的行为一样
        void setResources(Resources r) {
            if (r instanceof CompatResources) {
                ((CompatResources) r).setContext(this);
            }
            mResources = r;
        }
        
        @Override
        public Resources getResources() {
            return mResources;
        }
        
        
        /* ---------- 主题相关 ------------ */
        
        @Override
        public void setTheme(int resId) {
            synchronized (mSync) {
                if (mThemeResource != resId) {
                    mThemeResource = resId;
                    initializeTheme();
                }
            }
        }
        
        // 直接创建一个 Themem 对象,相比 ContextThemeWrapper,少了一部分内容
        private void initializeTheme() {
            if (mTheme == null) {
                mTheme = mResources.newTheme();
            }
            mTheme.applyStyle(mThemeResource, true);
        }
    
        @Override
        public Resources.Theme getTheme() {
            synchronized (mSync) {
                // 和 ContextThemeWrapper 基本一样
                if (mTheme != null) {
                    return mTheme;
                }
    
                mThemeResource = Resources.selectDefaultTheme(mThemeResource,
                        getOuterContext().getApplicationInfo().targetSdkVersion);
                initializeTheme();
    
                return mTheme;
            }
        }
    
    }
    

    从代码中可以看出,ContextImpl 和 ContextThemeWrapper 最大的区别就是没有一个 Configuration 而已,其它的行为大致是一样的。另外,ContextImpl 可以用于创建 Activity、Service 以及 Application 的 mBase 成员,这个 Base Context 时除了参数不同,它们的 Resource 也不同。需要注意的是,createActivityContext 等方法中 setResource 是 mBase 自己调用的,Activity、Service 以及 Application 本身并没有执行 setResource。

    小结

    1. ContextWrapper、ContextThemeWrapper 都是 Context 的代理类,二者的区别在于 ContextThemeWrapper 有自己的 Theme 以及 Resource,并且 Resource 可以传入自己的配置初始化

    2. ContextImpl 是 Context 的主要实现类,Activity、Service 和 Application 的 Base Context 都是由它创建的,即 ContextWrapper 代理的就是 ContextImpl 对象本身

    3. ContextImpl 和 ContextThemeWrapper 的主要区别是, ContextThemeWrapper 有 Configuration 对象,Resource 可以根据这个对象来初始化

    4. Service 和 Application 使用同一个 Recource,和 Activity 使用的 Resource 不同

    Activity Context、Service Context、Application Context、Base Context 有什么区别?

    Activity Context

    先看 Activity,Activity 在启动时,最终会执行 ActivityThread 的 performLaunchActivitiy:

    public final class ActivityThread {
    
        private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
            ...
            // 这个 Context 将会作为 Activity 的 Base Context
            ContextImpl appContext = createBaseContextForActivity(r);
            Activity activity = null;
            try {
                ClassLoader cl = appContext.getClassLoader();
                // 创建 Activity
                activity = mInstrumentation.newActivity(
                        cl, component.getClassName(), r.intent);
                StrictMode.incrementExpectedActivityCount(activity.getClass());
            } catch (Exception e) {
                ...
            }
    
            try {
                // 创建 Application
                Application app = r.packageInfo.makeApplication(false, mInstrumentation);
    
                if (activity != null) {
                    // 初始化 Activity,注意参数 appContext
                    activity.attach(appContext, ...);
                    ...
                }
    
            } catch (...) {
                ...
            }
    
            return activity;
        }
        
        private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
            ContextImpl appContext = ContextImpl.createActivityContext(...);
            ...
        }
        
    }
    

    可以看到,Activity 的 Base Context 就是上面分析过的 ContextImpl 的 createActivityContext 创建的。

    同时,Service 的 Base Context 的创建过程和 Application 一样,调用的都是 ContextImpl 的 createAppContext,即 Service Context 和 Application Context 的 Resource 是相同的。因此这里跳过 Service,下面看一下 Application Context。

    Application Context

    在上面 ActivityThread 的 performLaunchActivity 方法中,可以看到一个 makeApplication 的调用,它是 LoaedApk 的方法:

    public final class LoadedApk {
    
        private Application mApplication;
    
        public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) {
            if (mApplication != null) {
                return mApplication;
            }
    
            Application app = null;
    
            try {
                // 创建 Base Context
                ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
                // 创建 Application 并设置 Base Context
                app = mActivityThread.mInstrumentation.newApplication(
                        cl, appClass, appContext);
                appContext.setOuterContext(app);
            } catch (Exception e) {
                ...
            }
            // Application 创建成功,赋值给 mApplication
            mApplication = app;
            
            ...
            
            return app;
        }
        
        // 获取 mApplication
        Application getApplication() {
            return mApplication;
        }
        
    }
    

    看完上面的代码,其实已基本能猜出 Application 及其 Base Context 的创建过程了,但为了完整地了解整个过程,下面还是看一下 Instrumentation 的实现:

    public class Instrumentation {
    
        public Application newApplication(ClassLoader cl, String className, Context context) throws ... {
            return newApplication(cl.loadClass(className), context);
        }
        
        static public Application newApplication(Class<?> clazz, Context context) throws ... {
            // 反射创建 Application 对象
            Application app = (Application)clazz.newInstance();
            app.attach(context);
            return app;
        }
        
    }
    
    public class Application extends ContextWrapper implements ComponentCallbacks2 {
        /* package */ final void attach(Context context) {
            // 调用父类的 attachBaseContext 以设置 mBase
            attachBaseContext(context); 
        }
    }
    

    可以看到,Instrumentation 是使用反射的方法创建 Application 对象,创建完毕后,会执行 Application 的 attach 方法设置 mBase 成员。

    Application 及其 Base Context 的创建过程我们了解了,接下来看一下 getApplicationContext 的实现:

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

    从代码中可以看出,getApplicationContext 的返回值可能有两个:第一个是 LoadedApk 的 getApplication 方法,这个方法的返回值就是刚刚创建的 Application 对象;第二个是 ActivityThread 的 getApplication 方法:

    public final class ActivityThread {
    
        Application mInitialApplication;
        
        public Application getApplication() {
            return mInitialApplication;
        }
        
        public static ActivityThread systemMain() {
            // 创建 ActivityThread
            ActivityThread thread = new ActivityThread();
            thread.attach(true);
            return thread;
        }
        
        private void attach(boolean system) {
            mSystemThread = system;
            if (!system) {
                ...
            } else {
                try {
                    mInstrumentation = new Instrumentation();
                    // 注意参数 getSystemContext().mPackageInfo
                    ContextImpl context = ContextImpl.createAppContext(
                            this, getSystemContext().mPackageInfo);
                    // 创建 Application
                    mInitialApplication = context.mPackageInfo.makeApplication(true, null);
                    mInitialApplication.onCreate();
                } catch (Exception e) {
                    ...
                }
            }
            ...
        }
        
    }
    

    ActivityThread 中的 mInitialApplication 是在 systemMain 方法执行时创建的,而这个方法又是 SystemServer 启动时调用的,结合参数 getSystemContext().mPackageInfo,因此个人推测 mInitialApplication 对应的是系统的某个 apk,即系统级别的 Application,但具体是不是这样,目前还没有深入研究过,有兴趣的可以自己研究。

    为什么不推荐使用 Base Context?

    一般情况下,使用代理而不直接使用某个对象,目的可能有两个:

    1. 定制自己的行为
    2. 不影响原对象

    其中 Servcie 和 Application 的父类 ContextWrapper 完全没有自定义的行为,而 Activity 的父类 ContextThemeWrapper 则自定义了 Resource 以及 Theme 的相关行为,因此,个人理解:

    1. 对于 Service 和 Application 而言,不推荐使用 Base Context,是担心用户修改了 Base Context 而导致错误的发生
    2. 对于 Activity 而言,除了担心用户的修改之外,Base Context 和 Activity 本身对于 Reource 以及 Theme 的相关行为是不同的(如果应用了 Configuration 的话),使用 Base Context 可能会出现无法预期的现象

    对于 Activity 的 getResource 问题,我写了一份代码来验证:

    public class MainActivity extends AppCompatActivity {
    
        private Configuration mOverrideConfiguration;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Log.i(TAG, "getResources: " + getResources() + ", getBaseContext().getResources():"
                    + getBaseContext().getResources());
        }
        
        // 因为 Android 会在 onCreate 之前自动调用 getResource
        // 因此需要在这里执行 applyOverrideConfiguration
        @Override
        public Resources getResources() {
            if (mOverrideConfiguration == null) {
                mOverrideConfiguration = new Configuration();
                applyOverrideConfiguration(mOverrideConfiguration);
            }
            return super.getResources();
        }
        
    }
    

    输出(我用的是小米手机):

    getResources: android.content.res.MiuiResources@3c660a7, 
    getBaseContext().getResources():android.content.res.MiuiResources@5143954
    

    可以看到,就像源码显示的那样,应用了 Configuration 之后,Activity 的 getResource 方法返回的和 getBaseContext().getResources() 方法返回的不是同一个对象

    小结

    1. Activity、Service 和 Application 的 Base Context 都是由 ContextImpl 创建的,且创建的都是 ContextImpl 对象,即它们都是 ContextImpl 的代理类

    2. getApplicationContext 返回的就是 Application 对象本身,一般情况下它对应的是应用本身的 Application 对象,但也可能是系统的某个 Application

    3. 对于 Service 和 Application 而言,不推荐使用 Base Context,是担心用户修改了 Base Context 而导致错误的发生

    4. 对于 Activity 而言,除了担心用户的修改之外,Base Context 和 Activity 本身对于 Reource 以及 Theme 的相关行为是不同的(如果应用了 Configuration 的话),使用 Base Context 可能会出现无法预期的现象

    总结

    Context 的继承关系如下:

    Context 家族

    Context 相当于 Application 的大管家,主要负责:

    1. 四大组件的交互,包括启动 Activity、Broadcast、Service,获取 ContentResolver 等
    2. 获取系统/应用资源,包括 AssetManager、PackageManager、Resources、System Service 以及 color、string、drawable 等
    3. 文件,包括获取缓存文件夹、删除文件、SharedPreference 相关等
    4. 数据库(SQLite)相关,包括打开数据库、删除数据库、获取数据库路径等
    5. 其它辅助功能,比如设置 ComponentCallbacks,即监听配置信息改变、内存不足等事件的发生

    ContextWrapper、ContextThemeWrapper、ContextImpl 的区别:

    1. ContextWrapper、ContextThemeWrapper 都是 Context 的代理类,二者的区别在于 ContextThemeWrapper 有自己的 Theme 以及 Resource,并且 Resource 可以传入自己的配置初始化
    2. ContextImpl 是 Context 的主要实现类,Activity、Service 和 Application 的 Base Context 都是由它创建的,即 ContextWrapper 代理的就是 ContextImpl 对象本身
    3. ContextImpl 和 ContextThemeWrapper 的主要区别是, ContextThemeWrapper 有 Configuration 对象,Resource 可以根据这个对象来初始化

    Activity Context、Service Context、Application Context、Base Context 的区别:

    1. Activity、Service 和 Application 的 Base Context 都是由 ContextImpl 创建的,且创建的都是 ContextImpl 对象,即它们都是 ContextImpl 的代理类
    2. Service 和 Application 使用同一个 Recource,和 Activity 使用的 Resource 不同
    3. getApplicationContext 返回的就是 Application 对象本身,一般情况下它对应的是应用本身的 Application 对象,但也可能是系统的某个 Application

    为什么不推荐使用 Base Context:

    1. 对于 Service 和 Application 而言,不推荐使用 Base Context,是担心用户修改了 Base Context 而导致错误的发生
    2. 对于 Activity 而言,除了担心用户的修改之外,Base Context 和 Activity 本身对于 Reource 以及 Theme 的相关行为是不同的(如果应用了 Configuration 的话),使用 Base Context 可能出现无法预期的现象

    相关文章

      网友评论

          本文标题:深入理解 Android 中的各种 Context

          本文链接:https://www.haomeiwen.com/subject/eejokqtx.html