前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android老司机被打脸!Dialog 对应的 Context 必须是 Activity吗?

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

原创
作者头像
Android技术干货分享
修改2021-06-29 10:28:25
3.7K0
修改2021-06-29 10:28:25
举报
文章被收录于专栏:Android技术分享Android技术分享

打脸记录: A:Dialog传入的上下文必须为Activity这对么 我:这个对呀,需要activity的token A:可以说对,但是也没那么对。我传入Dialog的context-直都没用activity 我:那第一个dialog的context应该是需要activity的 A:没有Activity的应用-样是可以显示Dialog的,setType(WindowManager.LayoutParams.TYPE_ SYSTEM_ ALERT) 我:嗯..学到了,我要总结下,发个文章。

后来发现我的回答确实错了,于是通过每日一问分享给大家。

于是有了本文,我负责被打脸,小缘负责解答,我反正不会被打脸第二次了,希望大家也能更清晰的认识这一块。

问题

在我们的印象里,如果构造一个 Dialog 传入一个非 Activiy 的 context,则可能会出现 bad token exception。

今天我们就来彻底搞清楚这一块,问题来了:

1、为什么传入一个非 Activity 的 context 会出现错误? 2、传入的 context 一定要是 Activity 吗?

解答

1.先来看第二问:创建Dialog对象依赖的Context必须是Activity吗?

相信大家曾经都有遇到过需要在Application或者Service里弹出Dialog的情景,就算平时做的正式项目没有这种需求,那也应该在刚开始学习Android或者写Demo的时候试过。

所以对于这个问题,回答肯定是:不是的。

在创建Dialog对象时,context参数传Activity和传Service或Application之类的非Activity的Context对象有什么区别呢?

有经验的同学会说,想要通过非Activity对象创建并正常显示Dialog,首先必须拥有SYSTEM_ALERT_WINDOW权限,还有,在调用Dialog.show方法之前,必须把Dialog的Window的type指定为SYSTEM_WINDOW类型,比如TYPE_SYSTEM_ALERT或TYPE_APPLICATION_OVERLAY。

没有满足第一个条件的话,那肯定会报permission denied啦。

如果在show之前没有指定Window的type为SYSTEM_WINDOW类型,一样会发生BadTokenException的,message是token null is not valid; is your activity running?。

为什么会这样?

常规的Dialog的容器是Activity,所以它窗口属性的token引用的就是Activity的Token。到了WMS那边会根据这个Activity的Token来找到对应的ActivityRecord实例(其实是根据Token来查找对应的容器),然后把Dialog对应的WindowState添加到ActivityRecord里面。注意! 如果在查找容器这一步,没有找到对应实例的话,就会抛出一个BadTokenException(token null is not valid; is your activity running?)

查找容器还跟Context实例有关系吗?使用Service或Application就找不到容器,换成Activity就能找到,这是为什么?

肯定有关系啦,别忘了Dialog在show方法里是通过WindowManager来添加View的,而这个WindowManager对象就是从Context的getSystemService(WINDOW_SERVICE)方法获得的。

重点来了:因为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方向时,就已经开始梳理自己的成长路线了,包括技术要怎么系统地去学习,都列得非常详细。

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

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 问题
  • 解答
    • 1.先来看第二问:创建Dialog对象依赖的Context必须是Activity吗?
      • 2.现在来回答第一问:为什么使用非Activity来创建并弹出Dialog,有时会发生BadTokenException?
      • 总结
      相关产品与服务
      容器服务
      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档