Android老司机被打脸!Dialog 对应的 Context 必须是 Activity吗?

**重点来了:**因为Activity重写了Context的getSystemService方法,在获取的WINDOW_SERVICE的时候返回了Activity主Window的WindowManager对象。当然了,这个主Window的WindowManager对象也没有什么特别之处,只是它里面的mParentWindow指向的是主Window(其他非Activity的Context的WindowManager.mParentWindow默认都是null)。

WindowManagerGlobal在addView的时候,如果检查到mParentWindow不为null的话,就会对窗口属性(即上一个回答中说到的mWindowAttributes)的token进行赋值,它的逻辑是这样的:

  • 如果窗口类型为SUB_WINDOW(即子窗口),就会把mParentWindow对应的ViewRootImpl的mWindow赋值给token(上一个回答也有相关介绍);

  • 窗口类型为SYSTEM_WINDOW(系统级别的窗口,比如ANR Dialog),则不会对token进行赋值。因为普通应用的Window等级比系统Window低,所谓小庙容不下大佛;

  • 窗口类型为APPLICATION_WINDOW(Activity主Window和普通的Dialog就是这个类型),会把mParentWindow的mAppToken(也就是所属Activity的mToken)赋值给token;

根据上面这个规则,可以联想到会有两种情况导致窗口属性的token为null(token为null就肯定找不到容器啦),一种是创建Dialog时传了非Activity的Context,另一种是Dialog的Window.type指定为SYSTEM_WINDOW。

为什么非要一个Token?

这是因为在WMS那边需要根据这个Token来确定Window的位置(不是说坐标),如果没有Token的话,就不知道这个窗口应该放到哪个容器上了。

那为什么把Window的type指定为SYSTEM_WINDOW类型就能找到容器了呢?

其实一样没有找到,只是在获得SYSTEM_ALERT_WINDOW权限之后,会即时创建一个WindowToken而已(ActivityRecord也是继承自WindowToken),然后会把这个新创建的WindowToken附加到特定的容器上。

来看图:

常规的Dialog显示,是这样的。

最底的那个绿色的WindowState,就是Dialog的窗口。

把Dialog的Window.type指定为SYSTEM_WINDOW之后,是这样的:

右边最底的那个WindowState就是SYSTEM_WINDOW类型的Dialog窗口,在层级关系上,跟隔壁的ActivityRecord是相等的。

Dialog窗口所在容器,就是刚刚说到的那个即时创建的WindowToken。

其实其他系统级别的窗口也是放置在这个WindowToken的父级容器DisplayArea.Tokens里面的,就像这样:

噢对了,来了解一下WMS这边的各个容器的关系吧(深色箭头是extends的意思):

(试了好多办法,一张完整的图都没法让大家在手机上看清,于是我干了成两半)

2.现在来回答第一问:为什么使用非Activity来创建并弹出Dialog,有时会发生BadTokenException?

主要是因为非Activity的Context它的WindowManger没有ParentWindow,导致在WMS那边找不到对应的容器,也就是不知道要把Dialog的Window放置在何处。

还有一个原因是没有SYSTEM_ALERT_WINDOW权限(当然要加权限啦,DisplayArea.Tokens的子容器,级别比普通应用的Window高,也就是会显示在普通应用Window的前面,如果不加权限控制的话,被滥用还得了)。

在获得SYSTEM_ALERT_WINDOW权限并将Dialog的Window.type指定为SYSTEM_WINDOW之后能正常显示,是因为WMS会为SYSTEM_WINDOW类型的窗口专门创建一个WindowToken(这下就有容器了),并放置在DisplayArea.Tokens里面(这下知道放在哪里了)。

总结

Show一个普通的Dialog需要的并不是Activity本身,而是一个容器的token,我们平时会传Activity,只不过是Activity刚好对应WMS那边的一个WindowState的容器而已。

最后不用多说,相信大家都有一个共识:**无论什么行业,最牛逼的人肯定是站在金字塔端的人。**所以,想做一个牛逼的程序员,那么就要让自己站的更高,成为技术大牛并不是一朝一夕的事情,需要时间的沉淀和技术的积累。

关于这一点,在我当时确立好Android方向时,就已经开始梳理自己的成长路线了,包括技术要怎么系统地去学习,都列得非常详细。

这里最后分享耗时一年多整理的一系列Android学习资源:Android源码解析、Android第三方库源码笔记、Android进阶架构师七大专题学习、历年BAT面试题解析包、Android大佬学习笔记等等。

这些内容均免费分享给大家,需要完整版的朋友,点这里可以看到全部内容。或者点击 【这里】 查看获取方式。

最后,希望文章对你有帮助。

最后

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

下面分享的腾讯、头条、阿里、美团、字节跳动等公司2019-2021年的高频面试题全套解析,博主还把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,下面只是以图片的形式给大家展示一部分。

image

知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。

image

由于篇幅有限,下面只是以图片的形式给大家展示一部分。

[外链图片转存中…(img-iIIGqxDU-1719752362133)]

知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。

[外链图片转存中…(img-heYyjEve-1719752362134)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值