[toc]
PopupWindow 的 BadTokenException
2.0.0 版本出现这个崩溃
1 | Fatal Exception: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running? |
1 初步分析
原始的代码
1 | private fun showReceiveGiftTips(tips: String) { |
我们现在找到抛出异常的地方
ViewRootImpl#setView
1 | /** |
2 初步解决
看到这个问题,一般会认为是 Activity 已经 finished 或者应 destroyed 了,所以我们在前面加判断。
1 |
|
我们开心的修复,在 2.0.1 跟了出去
然而,还是有问题
3 分析过程
第一次解决尝试失败,我们在重新审视这个崩溃。
下面是平时我们遇到的
1 | android.view.WindowManager$BadTokenException: Unable to add window — token android.os.BinderProxy@447a6748 is not valid; is your activity running? |
结合抛出的异常信息,我们发现 chikii 抛出的这个 BadTokenException token 的值为空。它和我们平时遇到的 BadTokenException 有点不一样.
1 | WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running? |
这个 token 是 null 就是突破口
我们先看看这个 token 是从哪里来的
3.1 PopupWindow#show 的流程
1 | @startuml |
说明:
①
PopupWindow#showAtLocation
在这个方法拿到 anchor 锚点 View 的 windowToken,然后一路传着下去
仍然会
1 | public void showAtLocation(View parent, int gravity, int x, int y) { |
②③
创建 WindowManager.LayoutParams
1 | // PopupWindow#showAtLocation |
④
PopupWindow#invokePopup
将 DecorView 添加到 WindowManager 中
1 |
|
⑤
WindowManagerGlobal#addView 里面创建 ViewRootImpl 并调用 ViewRootImpl 的 addView 方法
1 | // WindowManagerGlobal#addView |
⑥
调用 WindowSession.addToDisplay 显示 PopuWindow
1 | //ViewRootImpl#setView |
现在整个调用的 链路都非常清楚了,调用 HomeReceiveGiftTipsPopupWindow 的 锚点 View 的 token 为空,即
fl_gift 的token 为空
1 | mHomeReceiveGiftTipsPopupWindow = HomeReceiveGiftTipsPopupWindow(context!!) |
3.2 View 的 token 来源
1 | // View# getWindowToken |
View 的 Token 是从 mAttachInfo 拿的,因此要知道 mAttachInfo 是怎么来的
3.2.1 View$AttachInfo
AttachInfo 是 View 的一个内部类,里面包含信息主要有
1 | // View$AttachInfo.java |
AttachInfo 的创建在 ViewRootImpl
1 | // ViewRootImpl.java |
3.2.2 AttachInfo 传递给 View 的过程
1 | ViewRootImpl#setView |
View#dispatchAttachedToWindow
1 |
|
可以看到 View 中的 token 是要经过 View#dispatchAttachedToWindow 方法才能有值的,这要求我们在显示 PopupWindow 的时候,要选择相应的时机。
4 最终解决
通过上面的 PopupWindow 的 show 流程和 View 的token 赋值流程的分析,
在去看看 为啥我们在第一步加了对 Activity 状态的判断还出现崩溃。
主要是因为锚点 View 的 token 为空导致的。
导致 token 为空,也是因为调用 show 的时机不对。
解决办法是在调用 PopupWindow 的 show 之前先判断一下锚点 View 的 applicationWindowToken ,并且调用的时机也要调整一下,不能在 View 一创建的时候就调用。
1 | fun showReceiveGiftTips(tips: String?) { |
1. 后续在使用 PopupWindow 的时候要注意调用 show 的时机
2. show 之前最好判断一下锚点 View 的 token 和 Activity 的状态