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

news2024/10/5 14:23:21

    这篇文章是基于我四年前的一篇文章进行更正和深入探究。背景是,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是什么呢?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/695366.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

看看螯合物前体多肽试剂DOTA-E[c(RGDfK)2]的全面解析吧!

【产品描述】 DOTA-E[c(RGDfK)2]螯合物前体多肽试剂,RGD肽指含有Arg-Gly-Asp三个氨基酸组成的序列多肽,可以提供大量的RGD直线肽,RGD环肽,RGD双环肽、RGD模拟肽等,也可以根据客户需求定制RGD肽。 DOTA-E [c (RGDfK) 2…

6.3 B树

多路平衡查找树 1.定义 B树的阶:B树中所有结点的孩子个数的最大值,表示成m m阶B树:空树或者满足如下特性的m叉树 特性: 1.树中每个结点最多子树 m 关键字m-1 2.根节点不是终端结点,至少有两棵子树 3.根结点除外&…

Java 遍历List的两种方式

可参考文章 Notepad编译并运行java代码_notepad怎么运行java代码_西晋的no1的博客-CSDN博客 中的第二种方法测试下述代码。 在java中,可以使用for循环和使用for-each循环两种方式遍历List。 1.使用for循环遍历List 2.使用for-each遍历List 注意:使用for-…

Java使用Maven工程操作OpenGL ES绘制三角形和圆形;绘制完成后操作键盘控制然图形移动

OpenGL ES 绘制三角形&#xff0c;操作键盘移动位置 PS&#xff1a;想快速看到效果的小伙伴&#xff0c;可以在引入依赖后&#xff0c;先跳到完整代码部分 第一步&#xff1a;依赖引入 <properties><lwjgl.version>3.2.3</lwjgl.version><joml.version&…

mysql 密码丢失的解决方案

问题描述 mysql 密码丢失的解决方案 解决方案&#xff1a; 停止服务; 重新启动服务&#xff1a;mysqld.exe --skip-grant-tables //启动服务器但是跳过权限; 当前启动的服务器没有权限概念&#xff1a;非常危险&#xff0c;任何客户端&#xff0c;不需要任何用户信息都可…

Pycharm连接远端Python环境操作Spark

文章目录 1. 创建工程&#xff0c;指定远端python解析器2. 添加远端python解析器3.配置完成4.本地文件自动同步远端5.删除远端python解析器(非必须操作&#xff0c;重新配置时参考该项)6. 文件模板配置 远程连接方案, 允许程序员连接远端测试环境, 确保环境的统一, 避免各种环境…

消息队列中间件(一)

场景 流量削峰 应用解耦 异步处理 分类 ActiveMQ 优&#xff1a;单机吞吐万级&#xff0c;时效性ms级&#xff0c;可用性高&#xff08;主从架构&#xff09;&#xff0c;可靠性高&#xff08;丢失率低&#xff09; 缺&#xff1a;官方维护少&#xff0c;高吞吐场景较少…

QT Creator上位机学习(二)基础控件及信号与槽

c# 系列文章目录 文章目录 布局控件信号与槽程序图标使用技巧 布局控件 美化界面的时候&#xff0c;经常需要进行一些控件的布局&#xff0c;这时需要使用一些容器类。 在快捷栏出&#xff0c;也有一些布局设计的选择 如上图&#xff0c;其中涉及到几种编辑状态&#xff1…

Math简单学习

1.绝对值 就变个符号 public static double abs(double a) {return (a < 0.0D) ? 0.0D - a : a; }public static float abs(float a) {return (a < 0.0F) ? 0.0F - a : a;}public static int abs(int a) {return (a < 0) ? -a : a;}IntrinsicCandidatepublic sta…

Redis【实战篇】---- 优惠卷秒杀

Redis【实战篇】---- 优惠卷秒杀 1. 全局唯一ID2. Redis实现全局唯一ID3. 添加优惠券4. 实现秒杀哦下单5. 库存超卖问题分析6. 乐观锁解决超卖问题7. 优惠券秒杀 ---- 一人一单8. 集群环境下的并发问题 1. 全局唯一ID 每个店铺都可以发布优惠券&#xff1a; 当用户抢购时&…

封装websocket请求-----vue2

参考 (875条消息) 封装websocket请求-----vue项目实战&#xff08;完整版&#xff09;_vue websocket封装_winne雪的博客-CSDN博客https://blog.csdn.net/m0_38134431/article/details/105794108 一、在utils目录下创建websocket.js文件 import {Message} from element-ui /…

Mac 已经在.bash_profie中配置过sdk环境依然报zsh: command not found: adb

提示 前置条件 已经安装好Android studio 然后~/.bash_profile 或.bash_profile也已经配置sdk路径&#xff0c;打开第二个终端输入adb时提示zsh: command not found: adb 解决办法 打开终端输入下面命令 echo export ANDROID_HOME/Users/$USER/Library/Android/sdk >>…

近日,我处理了一个大文件导入 nginx HTTP/1.1“ 413 585的问题

今天&#xff0c;导入一个1万多条数据的excel文件&#xff0c;本地没有用到nginx&#xff0c;导入很顺畅 部署到了线上后&#xff0c;导入文件后后台并没有日志输出&#xff0c;说明没有进入后端 经过摸排&#xff0c;分析&#xff0c;最终发现&#xff0c;是nginx这关没过 …

智能相机的功能介绍

智能视觉检测相机主要是应用在工业检测领域图像分析识别、视觉检测判断。相机具有颜色有无判别、颜色面积计算、轮廓查找定位、物体特征灰度匹配、颜色或灰度浓淡检测、物体计数、尺寸测量、条码二维码识别读取、尺寸测量、机械收引导定位、字符识别等功能。相机带有HDMI高清视…

stm32使用clion移植canfestival(canopen)

官方网站 https://canfestival.org/index.html.en 非官方的下载地址 GitHub - ljessendk/CanFestival 每个版本的源码会有些差异&#xff0c;移植代码的时候最好把源码也一并移植 以下使用硬石h743开发板, 并使用TIM8作为FDCAN1的定时器 STM32CubeMax 设置APB1的频率为20…

vscode设置可以搜索包含node_modules中的文件

步骤3中删除掉node_modules&#xff0c;再搜索的时候&#xff0c;node_modules的匹配到代码也会展示出来了。 如果不想要被搜索文件包含node_modules,再添加上就可以。

数值分析算法 MATLAB 实践 数值优化算法

数值分析算法 MATLAB 实践 数值优化算法 黄金分割法 function [x,y,k_cnt ] Goldensection(fun, a, b, eps) %fun为优化函数&#xff0c;a为区间左侧值&#xff0c;b为区间右侧值&#xff0c;eps为精度 % 黄金分割法 k_cnt0;while(b - a > eps)x1 a 0.382 * (b - a);x2 a…

OpenXML库(office文档读写库)的安装

本体安装 OpenXml库是由微软维护的一个开源的Office文档读写库&#xff0c;其与其他类似用途的库的比较可以看到这篇文章。 在C#中使用OpenXml非常简单&#xff0c;只需要使用NuGet安装其程序包即可&#xff0c;流程如下(NuGet这东西真的是个神器啊&#xff01;)&#xff1a;…

探索嵌入式开发领域:单片机、ARM、Android底层的紧密联系

作为一个曾编写ARM教程和参与Android产品开发的专家&#xff0c;我发现单片机、ARM、嵌入式开发和Android底层开发之间存在紧密的联系。对于那些希望在嵌入式开发领域发展的人来说&#xff0c;了解这些领域的知识至关重要。为了帮助你更好地学习这些内容&#xff0c;我总结了一…

pytorch简单入门

PyTorch是一个基于Python的科学计算库&#xff0c;主要针对两类人群&#xff1a;NumPy使用者和深度学习研究人员。它提供了灵活的高效的GPU加速计算&#xff0c;并且具有广泛的工具箱&#xff0c;可以支持复杂的神经网络架构。 在本篇博客中&#xff0c;我将向您介绍如何入门…