概述
Toast 以其简单的交互和便捷的使用深受大家的喜爱,那么我们在使用过程中肯定也经常碰到下面的崩溃异常:
1 | android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@2b6ef40 is not valid; is your activity running? |
而且奇怪的是该问题集中出现在 Android 的某些版本上面,基于该问题的分析,需要我们通读一下 Android Toast 的源码,具体请参考我的博客Toast 源码分析。
本文只简单分析该问题的原因和解决方法。
问题原因
这个问题目前只在Android 7以及以下的版本中出现,为什么会这样呢?我们从源码中一探究竟。
先来看一下Android 7中 TN.handleShow
这个方法:
1 | public void handleShow(IBinder windowToken) { |
再来看一下 Android 8 版本中的代码:
1 | public void handleShow(IBinder windowToken) { |
看到没有,在 Android 8以及以上的版本中,在 mWM.addView()
代码添加了try catch 代码块,把可能抛出的 WindowManager.BadTokenException
捕获了,因此,当出现异常时,仅仅是 Toast 不会显示出来,而不会导致应用崩溃。
那么为什么会出现 WindowManager.BadTokenException
的异常呢?
我们知道,Toast 显示的时序控制是由 NotificationManagerService 来控制的,在显示 Toast 部分的代码中:
1 | void showNextToastLocked() { |
有个超时监听的代码:
1 | private void scheduleTimeoutLocked(ToastRecord r) |
当某个 Toast 通知显示进程显示后,就会发一个超时监听的消息,在规定时间到达后就会隐藏 Toast,并且把窗口 token 删除。
1 | //NotificationManagerService.java |
如果这是碰巧 UI 系统比较卡顿,在 WMS 进程删除 token 后,显示进程才去显示,那么就会抛出 BadTokenException 异常,只不过 Android 8以及以上系统会把这个异常捕获。
验证
我们通过下面的代码来验证一下这个问题。
1 | Toast.makeText(this,"test",Toast.LENGTH_SHORT).show(); |
在 Android 7和以下的版本版本中会出现崩溃,而在 Android 7 以上版本中,Toast 没有显示,但是没有出现崩溃。
解决方案
如果我们在 Toast.show
调用的地方加个 try catch 模块可以解决这个问题吗?因为 Toast.show
仅仅是发了个通知消息给 NotificationManagerService,真正需要显示时是 NotificationManagerService 通知 TN 对象来显示的。因此这个方法是不起作用的。TN.mHandler
对象来处理 NotificationManagerService 发出的消失消息并且调用 handleShow(token)
方法来显示。
根据 Android 消息机制 – 源码分析 的分析我们知道,Hanlder
处理消息的方法 handleMessage
也是在 dispatchMessage
方法中执行的,因此,我们只需要在 Hanlder
方法的 dispatchMessage
加上 try catch 块即可。
因此我们定义一个 Handler
装饰器,参考[Android Toast问题深度剖析(二)] (https://cloud.tencent.com/developer/article/1034223) 给出的解决方案。
1 | public class ToastUtils { |
运行一下测试代码:
1 | ToastUtils.showToast(this,"hello", Toast.LENGTH_LONG); |
在 Android 7 版本中也没有出现崩溃。
参考文章
[Android] Toast问题深度剖析(一):https://cloud.tencent.com/developer/article/1034225
[Android] Toast问题深度剖析(二):https://cloud.tencent.com/developer/article/1034223