Android的Context详解 - 揭开Context的神秘面纱

2023-11-11

    这篇文章是基于我四年前的一篇文章进行更正和深入探究。背景是,2019年4月份我在找工作,看到一个问题,问this,getBaseContext()、getApplication()、getApplicationContext()的区别。当时我写了简单的demo验证,得出了跟网上答案一致的结论。但就在昨天,我发现,这个问题或许还有其他的答案。

    这是四年前那篇文章:getBaseContext()、getApplication()、getApplicationContext()的区别_heart荼毒的博客-CSDN博客

目录

一、回顾之前的demo

二、一个偶然的发现

三、Context的继承关系

四、getBaseContext

1、Activity的attachBaseContext

2、AppCompactActivity的attachBaseContext


一、回顾之前的demo

    首先,这是我当时测试用到的demo,很简单,就默认创建的项目,MainActivity继承Activity。我直接实现了如下代码:

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.d("TTTT", "this:" + this);
        Log.d("TTTT", "getBaseContext():" + getBaseContext().toString());
        Log.d("TTTT", "getApplication():" + getApplication().toString());
        Log.d("TTTT", "getApplicationContext():" + getApplicationContext().toString());
    }
}

    运行后,打印的Log如下:

     于是,我基于demo和打印的log信息得出了这样的结论:

  • this获取到的是当前Activity的对象;
  • getApplication和getApplicationContext获取到的均为同一个Application对象。
  • getBaseContext()获取到的是ContextImpl。

     到这里,在2019年那次验证中,就结束了,可以说是浅尝辄止。其实基于这三条结论,可能会有同学跟现在的我一样,存在诸多疑惑。比如说:getBaseContext获取到的ContextImpl是什么?

二、一个偶然的发现

    我有一个习惯,我会时不时的review自己之前写过的一些文章,以防止因为当时的认知问题得出一些错误的结论。或者随着技术的更新迭代,一些结论有失偏颇。我会及时的去完善和更新之前的一些文章。也正是在看到那篇文章时,我突然发现一个问题,我当时的demo继承的是Activity,而不是时下流行的androidx中的AppCompactActivity。

    于是,接下来,我把MainActivity改成继承自AppCompactActivity:

public class MainActivity extends AppCompactActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.d("TTTT", "this:" + this.toString());
        Log.d("TTTT", "getBaseContext():" + this.getBaseContext().toString());
        Log.d("TTTT", "getApplication():" + this.getApplication().toString());
        Log.d("TTTT", "getApplicationContext():" + this.getApplicationContext().toString());
    }
}

    运行后,打印的log如下:

     可以看到,前两个结论站得住脚。而getBaseContext()获取的结果变了,由之前的ContextImpl变成了ContextThemeWrapper。那么,带着前面的问题以及新发现的问题,我们一起去揭开Context的神秘面纱。

三、Context的继承关系

     Context是一个抽象类,它有多个直接或间接的子类。首先,我们看下Context的继承关系:    

Context
├── ContextImpl
├── ContextWrapper
│   ├── Application
│   ├── Service
│   ├── ContextThemeWrapper
│   │   ├── Activity
│   │   │   ├── ComponentActivity
│   │   │   └── ... └── FragmentActivity
│   │   └── ...            └── AppCompatActivity
│   └── ...
└── ...

(1)ContextImpl    

上面我们也提到,Context是一个抽象类,那么他需要有个实现类。ContextImpl是Context的实现类,真正实现了Context中的所有方法。我们调用的各种Context类的方法,其实现均来自于该类。(注:Android系统源码的很多设计,都遵循这样的规则:抽象类X一定对应一个XImpl的实现类

(2)ContextWrapper

    ContextWrapper是Context的包装类,可以包装另一个Context对象,并在其基础上添加新的功能。

(3)ContextThemeWrapper

    ContextThemeWrapper是一个特殊的包装类,可以为应用程序的UI组件添加theme样式。从继承关系层级也可以看出来,Application和Service不需要UI样式。而Activity需要Theme,Activity就是直接继承自ContextThemeWrapper。

四、getBaseContext

    首先,我们看下getBaseContext方法。上面我们也提到过,ContextWrapper是Context的包装类,因此点击该方法后,直接进入到ContextWrapper中的getBaseContext的实现中。

看下mBase在哪里被赋值的。

    接下来,分别看下Activity和AppCompactActivity对attachBaseContext是如何重写的。

1、Activity的attachBaseContext

    首先是Activity的attachBaseContext:

    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(newBase);
        if (newBase != null) {
            newBase.setAutofillClient(getAutofillClient());
            newBase.setContentCaptureOptions(getContentCaptureOptions());
        }
    }

    Activity的attachBaseContext的调用,是在Activity的attach方法调用的:

    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, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
            IBinder shareableActivityToken) {
        attachBaseContext(context);
        ......................
}

    那Activity的attach方法是在哪里调用的?这就得从Activity的启动来说起,我们不在此去详细说,点击感兴趣可以自行百度。我直接抛出答案:在ActivityThread的performLaunchActivity方法去启动Activity。这个方法很长,感兴趣的自己看源码,我只截取关键代码:

    /**  Core implementation of activity launch. */
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ..............

        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            ................

        } catch (Exception e) {
            ......
        }

        try {
                .................

                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, r.activityConfigCallback,
                        r.assistToken, r.shareableActivityToken);

                .................

        } catch (SuperNotCalledException e) {
            throw e;

        } catch (Exception e) {
            ......
        }

        return activity;
    }

    通过上面的代码可以清晰地看到,attach方法传给Activity的Context就是ContextImpl。

2、AppCompactActivity的attachBaseContext

    接下来,一起看下AppCompactActivity的attachBaseContext方法:

    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(getDelegate().attachBaseContext2(newBase));
    }

   可以看到,调用了getDategate(),进而调用了AppCompactDelegate的attachBaseContext2方法,看下这个方法:

    /**
     * Should be called from {@link Activity#attachBaseContext(Context)}.
     */
    @NonNull
    @CallSuper
    public Context attachBaseContext2(@NonNull Context context) {
        attachBaseContext(context);
        return context;
    }

    调用了自身的attachBaseContext方法,看下:

    /**
     * @deprecated use {@link #attachBaseContext2(Context)} instead.
     */
    @Deprecated
    public void attachBaseContext(Context context) {
    }

    可以看到,是一个@deprecated的空方法。其实AppCompatDelegate也是一个抽象类,按照Android的通常设计原则,必然有一个AppCompactImpl的实现类。我们直接看下实现类AppCompatDelegateImpl的attachBaseContext2方法,也是一个非常长的方法,仍然只保留关键代码:

    @NonNull
    @Override
    @CallSuper
    public Context attachBaseContext2(@NonNull final Context baseContext) {
        ....................

        // If the base context is a ContextThemeWrapper (thus not an Application context)
        // and nobody's touched its Resources yet, we can shortcut and directly apply our
        // override configuration.
        if (sCanApplyOverrideConfiguration
                && baseContext instanceof android.view.ContextThemeWrapper) {
            final Configuration config = createOverrideAppConfiguration(
                    baseContext, modeToApply, localesToApply, null, false);
            if (DEBUG) {
                Log.d(TAG, String.format("Attempting to apply config to base context: %s",
                        config.toString()));
            }

            try {
                AppCompatDelegateImpl.ContextThemeWrapperCompatApi17Impl.applyOverrideConfiguration(
                        (android.view.ContextThemeWrapper) baseContext, config);
                return baseContext;
            } catch (IllegalStateException e) {
                if (DEBUG) {
                    Log.d(TAG, "Failed to apply configuration to base context", e);
                }
            }
        }

        // Again, but using the AppCompat version of ContextThemeWrapper.
        if (baseContext instanceof ContextThemeWrapper) {
            final Configuration config = createOverrideAppConfiguration(
                    baseContext, modeToApply, localesToApply, null, false);
            if (DEBUG) {
                Log.d(TAG, String.format("Attempting to apply config to base context: %s",
                        config.toString()));
            }

            try {
                ((ContextThemeWrapper) baseContext).applyOverrideConfiguration(config);
                return baseContext;
            } catch (IllegalStateException e) {
                if (DEBUG) {
                    Log.d(TAG, "Failed to apply configuration to base context", e);
                }
            }
        }

        // We can't apply the configuration directly to the existing base context, so we need to
        // wrap it. We can't create a new configuration context since the app may rely on method
        // overrides or a specific theme -- neither of which are preserved when creating a
        // configuration context. Instead, we'll make a best-effort at wrapping the context and
        // rebasing the original theme.
        if (!sCanReturnDifferentContext) {
            return super.attachBaseContext2(baseContext);
        }
        ...........

         // Next, we'll wrap the base context to ensure any method overrides or themes are left
        // intact. Since ThemeOverlay.AppCompat theme is empty, we'll get the base context's theme.
        final ContextThemeWrapper wrappedContext = new ContextThemeWrapper(baseContext,
                R.style.Theme_AppCompat_Empty);

        .........

        return super.attachBaseContext2(wrappedContext);
    }

    可以看到,有两段类似的代码分别instanceof不同版本的ContextThemeWrapper(android.view包和androidx包),最后的话,是把这个wrappedContext丢到了AppCompactDelegate的attachBaseContext2方法里。

    最后,简单总结下。这篇文章是为了更正自己4年前的一篇文章所写,通过从Context的继承关系以及继承不同的Activity去实现demo,回答开篇提到的this,getBaseContext()、getApplication()、getApplicationContext()等的区别。尤其是对getBaseContext()在Activity和AppCompactActivity里返回的Context对象不同进行了深究。在最后的最后,也抛出一个小问题:那么FragmentActivity的getBaseContext是什么呢?

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android的Context详解 - 揭开Context的神秘面纱 的相关文章

  • 如何在android中点击画布上绘制的圆圈?

    我正在开发一个人脸检测应用程序 在这个应用程序中 我必须在脸上的眼睛和嘴巴用户可以点击拖动圆圈 在检测到的人脸上根据自己设置位置 因此 所有圆圈都已成功绘制在脸上 但我无法单击特定圆圈并使用缩小选项在整个脸上移动 请建议我有关相同问题的正确
  • 更改首选项的背景颜色

    我有一个PreferenceCategory xml 文件 我已经在其中定义了所有首选项 我从扩展的类中调用它PreferenceActivity 我无法设置设置屏幕的背景 该屏幕是在如下所示的 xml 文件的帮助下显示的 请看我已经定义了
  • Whatsapp 在 Android 中共享音频文件时正在剪切音频文件

    我在共享格式不是 mp3 的音频文件时遇到问题 文件被共享 但长度较短 例如 如果文件有 10 秒 则仅共享 5 秒 如果我使用 mp3 格式 它会完全共享 但其他格式会出现问题 注意 该文件在其他应用程序 如Messenger 中共享没有
  • 更改操作栏标题文本颜色

    我正在尝试更改 ActionBar 中标题文本的颜色 但我似乎无法让它工作 这是我尝试使用的风格 在我的应用程序主题中我使用titleTextStyle
  • Sqlite 查询检查 - 小于和大于

    return mDb query DATABASE TABLE new String KEY ROWID KEY LEVEL KEY LEVEL gt 3 lt 5 null null null null 我究竟做错了什么 它返回的值全部高
  • 任务“:app:checkReleaseDuplicateClasses”执行失败

    我的 React Native Android 构建中突然出现构建问题 令人惊讶的是 它是早上建好的 没有做任何改变 但突然就失败了 这就是我得到的错误 知道为什么会发生这种情况吗 在 stack 和 GitHub 中也看到了一些类似的问题
  • 服务在后台运行?

    我正在构建的应用程序的功能之一是记录功能 我通过在服务中启动 MediaRecorder 对象来实现此目的 Intent intent new Intent v getContext RecordService class Messenge
  • 在屏幕上随机生成一个圆圈并将其设为绿色或红色

    所以我一直在尝试制作一个游戏应用程序 它可以在 Android 屏幕上随机显示带有文本的红色按钮或带有文本的绿色按钮 如果有人可以帮助我 我将不胜感激 另外 如果有人知道如何做到这一点 我想慢慢地产生更快的酷优势 谢谢 SuppressLi
  • 使用 POST 将数据从 Android 发送到 AppEngine Datastore

    抱歉 如果这是一个简单的问题 但我只是不知道我应该做什么 而且我认为我有点超出了我的深度 我想将数据从 Android 应用程序发送到在 Google App Engine 上运行的应用程序 数据必须从那里写入数据存储区 我的数据主要采用对
  • 用于代码生成的 ANTLR 工具版本 4.7.1 与当前运行时版本 4.5.3 不匹配

    我正在开发一个 Android 应用程序 当前使用 DSL 和一些库 突然构建给了我这个错误 任务 app kaptDebugKotlin 失败 用于代码生成的 ANTLR 工具版本 4 7 1 与当前运行时版本 4 5 3 不匹配 用于解
  • 将 ArrayList 保存在捆绑包 savingInstanceState 中

    ArrayList 是在类级别定义的 这些是我保存的实例方法 Override protected void onSaveInstanceState Bundle outState super onSaveInstanceState out
  • Android 在创建时出现 SQLiteException

    首先我想说我是android新手 所以如果这个问题太愚蠢我很抱歉 我正在为带有两个表的 SQLite 数据库编写一个内容提供程序 表格上是在导航抽屉活动中显示列表 第二个表格是在 ListFragment 中显示 每次启动应用程序时 我都会
  • Android接收通知打开和取消事件

    我从 webService 接收数据以生成自定义通知 我想追踪Intent要知道open 点击 或cancel 滑动 通知上的事件 以报告服务器进行分析 有没有听众onIntentStart or onIntentCanceled 也许是通
  • UnsupportedOperationException:特权进程中不允许使用 WebView

    我在用android sharedUserId android uid system 在我的清单中获得一些不可避免的权利 从 HDMI 输入读取安卓盒子 http eweat manufacturer globalsources com s
  • Android 将菜单项在操作栏中向左对齐

    我的应用程序中有一个操作栏 它显示我定义的菜单项res menu activity main xml 我的菜单项在操作栏上向右对齐 我希望它们左对齐 我为此找到的唯一解决方案使用了自定义操作栏 如下所示 将菜单项放置在 Honeycomb
  • 使用 eclipse 配置mockito 时出现问题。给出错误:java.lang.verifyError

    当我将我的mockito库添加到类路径中 并使用一个简单的mockito示例进行测试时 我尝试使用模拟对象为函数add返回错误的值 我得到java lang verifyerror 以下是用于测试的代码 后面是 logcat Test pu
  • 如何用 XML 制作双渐变(类似 iphone)

    如何使用 XML 制作这种可绘制渐变 我可以做一个从颜色 A 到颜色 B 的简单渐变 但我不知道如何在同一个可绘制对象中组合两个渐变 我终于找到了一个带有图层列表的解决方案 这对我来说已经足够好了
  • Admob - 没有广告可显示

    你好 我尝试制作一些在 Android 手机上显示广告的示例程序 并尝试在 v2 2 的模拟器上测试它 代码中的一切似乎都很好 但调试器中的 AdListener 表示 响应消息为零或空 onFailedToReceiveAd 没有广告可显
  • RecyclerView元素更新+异步网络调用

    我有一个按预期工作的回收视图 我的布局中有一个按钮可以填充列表 该按钮应该进行异步调用 根据结果 我更改按钮的外观 这一切都发生得很好 但是 当我单击按钮并快速向下滚动列表时 异步调用的结果会更新新视图的按钮 代替旧视图的视图 我该如何处理
  • Android:列“_id”不存在

    我收到这个错误 IllegalArgumentException 列 id 不存在 当使用SimpleCursorAdapter从我的数据库中检索 该表确实有这个 id柱子 注意到这是一个常见问题 我尝试根据网上的一些解决方案来解决它 但它

随机推荐

  • leetcode-135-分发糖果

    老师想给孩子们分发糖果 有 N 个孩子站成了一条直线 老师会根据每个孩子的表现 预先给他们评分 你需要按照以下要求 帮助老师给这些孩子分发糖果 每个孩子至少分配到 1 个糖果 相邻的孩子中 评分高的孩子必须获得更多的糖果 那么这样下来 老师
  • SpringBoot 集成sharding-jdbc 提示:Failed to configure a DataSource: ‘url‘ attribute is not specified ***

    问题描述 今天使用SpringBoot 集成sharding jdbc 4 1 1实现分库分表时报错 APPLICATION FAILED TO START Description Failed to configure a DataSou
  • 记录一次因now()函数应用周期性查不到数据的问题

    问题原因 查询sql使用了now 函数 测试环境数据库所在的容器日期不对 与实际时间晚8个小时 问题背景描述 某天下午快要下班的时候 大概五点的样子 某个测试小哥在系统里点击用户注册功能注册后 一切数据都正常生成后 登录新注册的用户 发现这
  • 基础算法题——Radio Transmission(KMP-next 妙用)

    Radio Transmission 解题思路 在KMP算法中 next l 记录的就是字符串最长的相同的前缀与后缀 也就是说在题目字符串中有一段字符串是重复出现的 那么减去重复出现的字符串以后 剩下的就是这个字符串最小的循环节 比较字符串
  • 19.RV1126_RV1109编写并移植nvp6021驱动

    文章目录 前言 确定硬件 配置设备树生成节点 前言 nvp6021是一个i2C器件 因此 应该编写I2C设备驱动 既然是I2C设备驱动 应该确定的有 使用的是哪一路I2C I2C设备地址是多少等 确定硬件 使用的是哪一路I2C 从上面可以看
  • 动态规划算法与典型例题

    目录 前言 一 动态规划要素 条件 二 动态规划算法设计步骤 三 复杂度分析 四 典型例题1 游艇租聘 五 典型例题2 0 1背包问题 六 典型例题3 跳台阶问题 七 典型例题4 强盗抢劫问题 总结 前言 动态规划也是一种分治思想 分治算法
  • html 在html文件中循环遍历数组并展示

    用html文件实现一个简单的遍历数组并输出到页面上面
  • unixbench测试CPU性能工具/mbw测试内存

    一 unixbench工具 UnixBench是一个类unix系 Unix BSD Linux 统下的性能测试工具 一个开源工具 被广泛用与测试linux系统主机的性能 Unixbench的主要测试项目有 系统调用 读写 进程 图形化测试
  • 【碎碎念随笔】1、回顾我的电脑和编程经历

    闲着无事 讲述一下我的计算机和代码故事 一 初识计算机 余家贫 耕植无钱买电脑 大约六年级暑假 我在姐姐哪儿第一次接触到了计算机 姐姐也是买的二手 计算机真有趣 在我眼中 计算机上寒假了世界上的好东西 是个聚宝盆 在计算机上 可以打小游戏
  • 对象之间的关系

    目录 1 依赖 2 关联 3 聚合 4 组合 5 继承 6 实现 Java的对象 类之间有四种关系 依赖 关联 组合 聚合 继承 实现 1 依赖 依赖 Dependency 一个对象的功能依赖于另一个对象 类比 人类生存依赖食物和空气 体现
  • 在C++遇到有些关键字或者函数被弃用的情况,比如xxx was declared deprecated

    在C 遇到有些关键字或者函数被弃用的情况 随着每一次C 的不断更新 可能都会有些函数或者关键字会被弃用 或者换成了其他的名字 这在编写代码的时候经常会碰到 碰到这种情况 可以在代码的第一行写上忽略此错误的句子 一般为 pragma warn
  • redis之如何配置jedisPool参数

    如何配置Pool的参数 JedisPool的配置参数很大程度上依赖于实际应用需求 软硬件能力 JedisPool的配置参数大部分是由JedisPoolConfig的对应项来赋值的 maxActive 控制一个pool可分配多少个jedis实
  • 项目管理在公司的主要作用是什么?

    项目管理不光是需要公司的支持和承接项目就可以的 还需要项目管理者多方面的把控 以及执行才会达到更好 那么项目管理的主要作用是什么了 1 提升项目本身的经济效益 项目管理通过对时间 成本的掌控 达到项目的经济效益最大化 保证了公司的良性发展
  • Ansible安装部署

    Ansible安装部署 Ansible概述 Ansible的作用 Ansible工作原理 Ansible的特点 Ansible安装部署 环境准备 管理端安装ansible 配置主机清单 ansible 命令行模块 1 command 模块
  • 基于时间序列的短期数据预测--ARMA模型的设计与实现(每个步骤附实现源码)

    本文demo源码 实验数据 传送门 引言 前面我有分享两篇关于时间序列模型的文章 一篇是 Holt Winters模型原理分析及代码实现 python 一篇是 LSTM模型分析及对时序数据预测的具体实现 python实现 holt wint
  • win32api.sendmessage模拟鼠标点击_安卓模拟器一键宏设置教程

    一 什么是一键宏 一键宏是指宏指令 主要作用是一键触发多个点击事件 游戏玩家可以用来设置一键连招 一键发言等功能 因此成为一键宏 二 如何设置一键宏 打开雷电模拟器 点击右侧栏按键按钮 找到 一键宏 按钮 点击拖拉到模拟器窗口你想摆放的位置
  • 【Spring Cloud】分布式必学springcloud(五)——Ribbon自定义负载均衡策略

    一 前言 在上一篇博客中 小编向大家介绍了负载均衡工具Ribbon 是不是很颠覆呀 是不是很好用呀 从中大家有没有感觉到他的负载均衡策略呀 对的 Ribbon内置的默认策略是轮询 在这篇博客中 小编就带大家领略一下Ribbon自定义策略 二
  • 计算机信息单位换算中的t是,算力单位换算(算力单位t)

    算力每隔千位划为一个单位 最小单位 H 1次 1000H 1K 1000K 1G 1000G 1T 1000T 1P 1000P 1E 1公斤力等于多磅力 n牛 顿 是力的国际单位 kg千克 是质量的国际单位 这两个单位可以通过加速度计算
  • 腾讯自选股任务 青龙脚本

    有python环境可以运行 青龙也可以运行 添加脚本自己定时规则 修改环境变量位自己的 加入进去即可 更新时间 2022 6 2 有python环境可以运行 青龙也可以运行 添加脚本自己定时规则 修改环境变量位自己的 加入进去即可 更新时间
  • Android的Context详解 - 揭开Context的神秘面纱

    这篇文章是基于我四年前的一篇文章进行更正和深入探究 背景是 2019年4月份我在找工作 看到一个问题 问this getBaseContext getApplication getApplicationContext 的区别 当时我写了简单