Application为啥不能作为Dialog的context?

大家好,相信大家在使用Dialog时,都有一个非常基本的认知:就是Dialog的context只能是Activity,而不能是Application,不然会导致弹窗崩溃:

图片

这个Exception几乎属于是每个Android开发初学者都会碰到的,但是。

前几天研究项目代码发现 , Application作为Dialog的context竟然不会崩溃?!!这句话说出来和本篇文章标题严重不符哈,这不是赤裸裸的打脸了吗。先别急,请大家跟着我的脚步,相信阅读完本篇文章就可以解答目前你心目中最大的两个疑惑:

  1. 如标题所言,为啥Application无法作为Dialog的context并导致崩溃?

  2. 项目中为啥又发现,Application作为Dialog的context可以正常显示弹窗?

1

窗口(包括Activity和Dialog)如何显示的?

这里怕有些童鞋不了解窗口(包括Activity和Dialog的)的显示流程,先简单的介绍下:

不管是Activity界面的显示还是DIalog的窗口显示,都会调用到WindowManagerImpl#addView()方法,这个方法经过一连续调用,会走到ViewRootImpl#setView()方法中。

在这个方法中,我们最终会调用到IWindowSession#addToDisplayAsUser()方法,这个方法是一个跨进程的调用,经过一番折腾,最终会执行到WMS的addWindow()方法。

在这个方法中会将窗口的信息进行保存管理,并且对于窗口的信息进行校验,比如上面的崩溃信息:“BadTokenException: Unable to add window”就是由于在这个方法中检验失败导致的;另外也是在这个方法中将窗口和Surface、Layer绘制建立起了连接(这句话说的可能不标准,主要对这块了解不多,懂得大佬可以评论分享下)。

接着开始在ViewRootImpl#setView()执行requestLayout()方法,开始进行渲染绘制等。

有了上面的简单介绍,接下来我们就开始先分析为啥Application作为Dialog的context会异常。

2

窗口离不开的WindowManagerImpl

上面也说了,窗口只要显示,就得借助WindowManagerImpl#addView()方法,而WindowManagerImpl创建流程在Application和Activity的差异,就是Application作为Dialog的context会异常的核心原因

我们就从下面方法作为入口进行分析:

context.getSystemService(WINDOW_SERVICE)
1. Application下WindowManagerImpl的创建

对于Application而言,getSystemService()方法的调用,最终会走到父类ContextWrapper中:

图片

而这个mBase属性对应的类为ContextImpl对象,对应ContextImpl#getSystemService():

图片

对应SystemServiceRegistry#getSystemService:

图片

SYSTEM_SERVICE_FETCHERS是一个Map集合,对应的key为服务的名称,value为服务的实现方式:

图片

Android会在SystemServiceRegistry初始化的时候将各种服务以及服务的实现方法注册到这个集合中:

图片

图片

接下来看下咱们关心的WindowManager服务的注册方式:

图片

到了这里,咱们就明白了,调用context.getSystemService(WINDOW_SERVICE)会返回一个WindowManagerImpl对象,核心点就在于WindowManagerImpl的构造函数,可以看到构造函数只传入了一个ContextImpl对象,我们看下其构造方法:

图片

本篇文章重要的地方来了:通过这种方法创建的WindowManagerImpl对象,其mParentWindow属性是null的。

2. Activity下WindowManagerImpl的创建

Activity重写了getSystemService()方法:

图片

而mWindowManager属性的赋值是发生在Activity#attach()方法中:

图片

这个mWindow属性对应的类型为Window类型(其唯一实现类为大家耳熟能详的PhoneWindow,其创建时机和Activity创建的时机是一起的),走进去看下:

图片

图片

经过一层层的调用,最终咱们的WindowManager是通过WindowManagerImpl#createLocalWindowManager创建的,并且参数传入的是当前的Window对象,即PhoneWindow。

图片

可以看到,该方法最终帮助咱们创建了WindowManagerImpl对象,关键点是其mParentWindow属性的值为上面传入的PhoneWindow,不为null

小结:

Activity获取到的WindManager服务,即WindowManagerImpl的mParentWindow属性不为空,而Application获取的mParentWindow属性为null。

文章开头我们简单介绍了窗口的显示流程,同时又知道实现窗口添加的关键类WindowManagerImpl的来头,有了这些铺垫,接下来我们就对窗口的显示进行一个比较深入的分析。

3

深入探究窗口的显示流程

这里我们就从WindowManagerGlobal#addView()方法说起,它是WindowManagerImpl#addView()方法的真正实现者。

WindowManagerImpl#addView():

图片

WindowManagerGlobal#addView():

图片

这一分析,就进入到了本篇文章最重要的一个方法的分析,如上面红框所示。

前面我们有讲过,对于Application获取的WindowManagerImpl,其mParentWindow属性为null,而Activity对应的mParentWindow不为null。

  1. 如果当前为Activity的窗口,或者借助Activity作为Context显示的Dialog窗口,其会走入到方法adjustLayoutParamsForSubWindow()中,对应的实现类为Window:

图片

type为窗口的类型,对于Activity的窗口还是对于Dialog的窗口,其对应类型为都为2(TYPE_APPLICATION),所以最终都会走到红框中的位置,最终给window对应的layoutparam对象的token属性赋值为mAppToken。

这个mAppToken可以简单理解为窗口的一种凭证,它是AMS在startActivity流程的时候被初始化的,然后传递给应用侧,最终再用来WMS进行窗口检验的。其中在AMS的startActivity流程中,会将这个AppToken作为key,并构造一个WindowToken对象作为value,写入到 DisplayContent#mTokenMap集合中,这部分详细的源码分析可以参考文章:Android高工面试(难度:四星):为什么不能使用 Application Context 显示 Dialog?

https://blog.csdn.net/chuhe1989/article/details/109515859

  1. 如果当前为application作为context显示的Dialog,mParentWindow为null,那就走不到adjustLayoutParamsForSubWindow()方法中,自然其xWindow#LayoutParam#token属性就是null。

咱们再次回到WindowManagerGlobal#addView()方法中,接下来会走到ViewRootImpl#setView()方法中,这个方法里最终会调用下面方法完成窗口真正的添加:

图片

其中这个mWindowSession对应是一个Binder对象,对应类型为IWindowSession,其真正的实现位于system_server侧的Session类,所以这里会发生跨进程通信,并将window的LayoutParam类型参数进行传入,我们继续看下Session#addToDiaplayAsUser方法:

图片

mService对应的实现类WindowManagerService,所以我们看下该类的addWindow方法:

# WindowManagerService
final HashMap<IBinder, WindowState> mWindowMap = new HashMap<>();

public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
            int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl[] outActiveControls) {

            WindowState parentWindow = null;
            final int type = attrs.type;
            //1. 
             if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
                parentWindow = windowForClientLocked(null, attrs.token, false);
              //...
            }
            //2. 
            final boolean hasParent = parentWindow != null;
            WindowToken token = displayContent.getWindowToken(
                    hasParent ? parentWindow.mAttrs.token : attrs.token);
            //3.
            if (token == null) {
                if (!unprivilegedAppCanCreateTokenWith(parentWindow, callingUid, type,
                        rootType, attrs.token, attrs.packageName)) {
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            }

            final WindowState win = new WindowState(this, session, client, token, parentWindow,
                    appOp[0], attrs, viewVisibility, session.mUid, userId,
                    session.mCanAddInternalSystemWindow);
}

# DiaplayConent
private final HashMap<IBinder, WindowToken> mTokenMap = new HashMap();

WindowToken getWindowToken(IBinder binder) {
    return mTokenMap.get(binder);
}

上面的代码是经过精简后的。

  1. 前面有提到,Dialog的窗口类型为2,所以不满足if的条件,自然parentWindow无法赋值,即为null;

  2. 这里hasParent自然就是false,调用方法getWindowToken()传入的参数就是应用侧Window#LayoutParam#token属性,其中借助前面分析,如果Application作为Dialog的context,这个token值是null;

    看下getWindowToken()方法,它会将上面的传入token作为key,从DisplayContent#mTokenMap这个集合中获取值,什么时候写入值呢:前面有提到过,在startActivity的流程中,会向这个集合中写入值。而这个传入的token就是之前startActivity流程中,写入到DisplayContent#mTokenMap这个集合中的key,所以自然是能够获取到对应的value,即WindowToken类型属性token不为null,自然走不到3处标记的条件分支中,窗口校验通过。

  3. 而Application作为Dialog的context时,传入的token是null,自然是无法获取到值,WindowToken 类型属性token为null,走到if分支中,会返回WindowManagerGlobal.ADD_BAD_APP_TOKEN ,当应用侧检测到返回值为这个时,就会出现文章一开头说的BadTokenException异常。

到了这里,相信你就明白了,为啥Application作为Dialog的context会导致崩溃,关键的分析就是上面的内容;

4

不让Application作为Dialog的context崩溃?

根据上面的分析结果,Application作为Dialog的context崩溃的真正原因就是应用侧传过来的LayoutParam#token对象是null的,既然这样,那我们在应用侧给Dialog的Window#LayoutParam#token属性赋值为Activity的Window#LayoutParam#token属性,就可以避免这场悲剧发生了,可以看到下面能正常显示弹窗:

图片

但是还是不建议大家这样做哈,毕竟如果在Dialog中使用到了这个Application的context进行Activity的跳转等其他未知行为,估计就会出现其他的幺蛾子了哈。

5

总结

本篇文章涉及到的源码有点多,重点在于以下几个地方:

  1. Activity和Application获取WindowManager在应用侧服务的区别;

  2. 将窗口添加到WMS侧,Activity和Application下WindowManagerImpl传参token的区别;

  3. WMS中对应窗口类型以及传入的token是否为null进行的一番检验,已经检验不通过导致应用侧发生BadTokenException异常。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

FFmpeg获取音视频流信息

文章目录 前言一、需求二、源码三、运行结果 前言 本文记录用 FFmpeg 获取视频流音频流的信息&#xff08;编码格式、分辨率、帧率、播放时长…&#xff09;&#xff0c;所用的工程基于上个博客编译成功的工程&#xff1a;使用FFmpeg4.3.1的SDK官方开发包编译ffmpeg.c 一、需求…

扩展边界opencv

扩展图像的边缘&#xff08;如上边增加50像素&#xff09;通常是通过添加额外的像素行来实现的 使用cv2.copyMakeBorder函数 valueborder_color指定了边框的颜色 import cv2 import numpy as np# 读取图像 image cv2.imread(th.jpg)# 设置边框宽度 top_border_width 50 # …

序列模型(4)—— Scaling Laws

本文介绍 LLM 训练过程中重要的 Scaling Laws&#xff0c;这是一个经验规律&#xff0c;指出了固定训练成本&#xff08;总计算量FLOPs&#xff09; C C C 时&#xff0c;如何调配模型规模&#xff08;参数量&#xff09; N N N 和训练 Token 数据量 D D D&#xff0c;才能实现…

【Emgu.CV教程】4.3、无缝融合应用之SeamlessClone()

SeamlessClone()函数才是真正的无缝克隆&#xff0c;它可以将一张小一点的图片&#xff0c;复制到另一张大一点的图片中&#xff0c;并且复制的位置可以用户自己定义&#xff0c;先看一下它的函数介绍&#xff1a; public static void SeamlessClone(IInputArray src, // 输入…

【STM32】| 01——常用外设 | USART

系列文章目录 【STM32】| 01——常用外设 | USART 失败了也挺可爱&#xff0c;成功了就超帅。 文章目录 前言1. 基础理论1.1 并行通信和串行通信1.2 同步通信和异步通信1.3 单工/半双工/全双工1.4 电平信号(RS232/TTL)和差分信号(RS485)1.5 端口(COM) 2. 串口理论2.1 串口物理…

Android - CrashHandler 全局异常捕获器

官网介绍如下&#xff1a;Thread.UncaughtExceptionHandler (Java Platform SE 8 ) 用于线程因未捕获异常而突然终止时调用的处理程序接口。当线程由于未捕获异常而即将终止时&#xff0c;Java虚拟机将使用thread . getuncaughtexceptionhandler()查询该线程的UncaughtExceptio…

cisp难不难?cisp如何备考通过率高?

*CISP 全称为Certified Information Security Professional&#xff0c;是国际上广受欢迎的信息安全专业认证之一。 对于许多信息安全领域的从业者来说&#xff0c;CISP认证是他们职业生涯中的重要一步。那么&#xff0c;CISP难不难呢?如何备考通过率更高呢?接下来&#xf…

electron自定义窗口和右键菜单样式

前言 electron默认沿用系统UI&#xff0c;并没有提供很多接口供使用者定制样式&#xff0c;如果想要完全自定义的样式&#xff0c;目前我能想到的方案只能是通过前端自定义样式&#xff0c;然后通过进程通信来实现系统基础功能&#xff1a;最大/小化、关闭、拖动窗口等。 效果…

关于java的冒泡排序

关于java的冒泡排序 我们前面的文章中了解到了数组的方法类Arrays&#xff0c;我们本篇文章来了解一下最出名的排序算法之一&#xff0c;冒泡排序&#xff01;&#x1f600; 冒泡排序的代码还是非常简单的&#xff0c;两层循环&#xff0c;外层冒泡轮数&#xff0c;里层依次比…

伺服电机:编码器原理与分类

什么是编码器&#xff1f; 编码器是将旋转位置的改变转换为电气信号。 编码器是伺服系统闭环控制不可缺少的部件&#xff0c;编码器应用在轴的闭环控制和大多数的自动化控制中。编码器为闭环控制提供位置或速度的实际测量值。 一、编码器的分类 从编码器的原理和产生的信号类…

Cypress.io:快速简单可靠的浏览器测试工具 | 开源日报 No.142

cypress-io/cypress Stars: 45.5k License: MIT Cypress.io 是一个快速、简单和可靠的浏览器测试工具&#xff0c;可以用于任何在浏览器中运行的内容。它支持 Mac、Linux 和 Windows 系统&#xff0c;并提供了安装指南。 hrvach/deskhop Stars: 4.1k License: GPL-3.0 DeskH…

ansible基础概念

一、【写在前面】 前面断更了几天&#xff0c;笔者被流感给干倒了&#xff0c;去拍了个核磁&#xff0c;给我脑子干成脱髓鞘了&#xff0c;也不知道是之前新冠导致的还是如何&#xff0c;哎要变成愚蠢的低级动物了……稍微恢复一点体力&#xff0c;今天赶快来博客水一水文章。…

代码随想录-刷题第五十二天

300. 最长递增子序列 题目链接&#xff1a;300. 最长递增子序列 思路&#xff1a;动态规划五步曲&#xff1a; dp[i]表示从0到i&#xff0c;以nums[i]结尾的最长递增子序列的长度。 递推公式&#xff1a;if(nums[i]>nums[j]) dp[i] max(dp[i], dp[j] 1) 位置i的最长升序…

MFC 多文档视图架构

目录 多文档视图架构 模仿多文档视图架构 执行流程 多文档视图架构 一个多文档视图架构运行后会是下面的样子&#xff1a; 内部的子框架窗口就相当于一个单文档视图架构&#xff0c;多文档视图架构就相当于在外面套一层框架窗口。 特点&#xff1a;可以管理多个文档(可以有…

Armv8-R AArch32 architecture概念学习

提示 该博客主要为个人学习&#xff0c;通过阅读官网手册整理而来&#xff08;个人觉得阅读官网的英文文档非常有助于理解各个IP特性&#xff09;。若有不对之处请参考参考文档&#xff0c;以官网文档为准。阅读该文章&#xff0c;可以先查看AArch64 Exception Model学习&…

SSM 基础知识点

1. IoC IoC—Inversion of Control&#xff0c;即“控制反转”&#xff0c;不是什么技术&#xff0c;而是一种设计思想。在 Java 开发中&#xff0c;IoC 意味着将你设计好的对象交给容器控制&#xff0c;而不是传统的在你的对象内部直接控制。 谁控制谁&#xff0c;控制什么&…

C语言中关于函数递归的理解

递归的概念&#xff1a;如果一个对象部分包含它自己,或者利用自己定义自己,则称这个对象是递归的;如果 一个过程直接或间接调用自己,则称这个过程是一个递归过程。递归的主要思考方式在于&#xff1a;将大事化小 我们先看一个例子 题目&#xff1a;输入一个无符号数&#xff0…

软考高级系统架构设计师考试经验分享

文章目录 1. 软考介绍&#xff08;1&#xff09;什么是软考&#xff08;2&#xff09;软考的作用&#xff08;3&#xff09;软考各科目的难度&#xff08;4&#xff09;考试时间&#xff08;5&#xff09;考试形式 2.系统架构设计师备考经验&#xff08;1&#xff09;辅导资料&…

【2024系统架构设计】 系统架构设计师第二版-嵌入式系统架构设计理论与实践

目录 一 嵌入式系统软件架构的原理 二 嵌入式系统软件架构的设计方法 三 案例分析 一 嵌入式系统软件架构的原理 🚀嵌入式系统的典型架构可以分为

51单片机四位数码管计算器 Proteus仿真程序

目录 概要 仿真图 部分代码 资料下载地址&#xff1a;51单片机四位数码管计算器 Proteus仿真程序 概要 1.系统通过4x4的矩阵键盘输入数字及运算符。 2.可以进行4位十进制数以内的加法运算&#xff0c;如果计算结果超过4位十进制数&#xff0c;则屏幕显示E 3.可以进行加法以外…
最新文章