深入理解 Android 中的各种 Context
发布日期:2021-07-19 12:30:02 浏览次数:8 分类:技术文章

本文共 19858 字,大约阅读时间需要 66 分钟。

前言

网上关于 Context 的文章也已经有不少了,比如值得参考的有:

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

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

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

上面这部分算一个讨论,下面正式进入正题,本文目录为:

  • Context 家族
  • Context 有什么用?
  • ContextImpl 、ContextWrapper、ContextThemeWrapper 有什么区别?
  • Activity Context、Service Context、Application Context、Base Context 有什么区别?
  • 为什么不推荐使用 Base Context?
  • 总结

Context 家族

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

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 getSystemService(@NonNull Class
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 对象本身。ContextWrapper里面有一个Context类型的成员变量mBase,当然它实际的类型是ContextImpl。ContextWrapper实现方法的时候调用了mBase的方法。

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

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

使用装饰者模式优势

  1. 避免代码重复,将一些通用的方法如starActivity()放到ContextImpl中,避免Activity,Application,Service中重复代码。
  2. 版本兼容,比如现在starActivity()等具有两种不同的实现。可以根据传入的具体类进行调用。比如增加一个ContextImplA,思考传统的类的实现方式和修饰者模式

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() 方法返回的不是同一个对象

总结

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 可能出现无法预期的现象

原文链接:https://juejin.im/post/5c1fab7d5188254eb05fbe48
 

转载地址:https://blog.csdn.net/jdsjlzx/article/details/97767353 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:从Zygote说到View——Zygote的启动流程及运行机制
下一篇:Android如何查找应用中调用的系统资源

发表评论

最新留言

路过按个爪印,很不错,赞一个!
[***.219.124.196]2024年03月29日 17时55分35秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

15拆解_收藏:15款劲芯微芯片无线充产品拆解 2019-04-21
弹出u盘_都说:U盘直接拔出不会丢失文件,“安全弹出”形同虚设,对吗? 2019-04-21
怎么查看elementui版本_2021新年 Vue3.0 + Element UI 尝鲜小记 2019-04-21
adreno630gpu参数_小米8搭载Adreno 630图形处理器 比荣耀play上的GPU Turbo更成熟 2019-04-21
带bitlocker解密的pe_如何在PE下解锁bitlocker 2019-04-21
lj245a引脚功能图_谁找到74254,74LS245芯片引脚的功能和功能图啊? 2019-04-21
sts 创建webservice项目_通过eclipse将Java生成webservice | 学步园 2019-04-21
python数字字符串和数字相加_数字和字符串 2019-04-21
python风控模型举例_一文搞定风控模型6大核心指标(附代码) 2019-04-21
java arraylist 写入文件_java-将自定义对象的ArrayList写入文件 2019-04-21
ice glacier2 java_ICE提纲之demo/Glacier2/callback(跨网回调) 2019-04-21
java 转发上传文件_java 后台请求其他接口转发文件 2019-04-21
Java get set 同步_java – getResultSet()“每个结果只能调用一次” 2019-04-21
java jmx 配置_为什么在配置JMX时Java打开3个端口? 2019-04-21
java thread回调_使用Runnable在Java中实现回调 2019-04-21
java 内存区_Java内存模型和Java内存区域的区别和联系? 2019-04-21
java定时任务监控_Spring定时任务使用及如何使用邮件监控服务器 2019-04-21
java crc32 使用_Java CRC32的用法 2019-04-21
java读取unicode_java怎么样将unicode解码读取?Java读取本地文件进 2019-04-21
java.io.file()_Java File getUsableSpace()方法 2019-04-21