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的神秘面纱 的相关文章

随机推荐

  • 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 的区别 当时我写了简单