更多问答 >>
-
2019-10-23 00:10
-
2019-10-28 21:13
-
2019-11-03 23:50
-
2019-11-08 23:06
-
每日一问 | 控件不都是矩形么?遇到多边形,这个怎么绘制,事件分发怎么处理嘞?
2019-11-13 01:08 -
2019-10-15 23:26
-
玩Android更新记录 [from 2019-10-02]
2019-10-08 21:44 -
2019-09-25 21:58
-
2019-09-23 01:01
-
2019-09-17 22:41
哪些场景下会出现这个异常?
源码分析:
AS中全局搜索BadTokenException,会在ViewRootImpl的
setView
方法中看到好几个抛出这个异常的代码:可以看到它是根据WindowSession的
来看看这个addToDisplay
方法的返回值做判断的,这个方法最终也是调用WMS(WindowManagerService)的addWindow
方法。addWindow
方法在什么情况下会返回以上7种Flag,首先是ADD_BAD_APP_TOKEN:除了
这两个token
为空会返回ADD_BAD_APP_TOKEN之外,还有一种情况就是当rootType
!=windowType
的时候也会返回ADD_BAD_APP_TOKEN。type
就是调用WindowManager的addView
方法时传进去的LayoutParams的type
,默认是TYPE_APPLICATION。所以后面那4个else if,也就是检查窗口类型是否匹配了。那这个token什么时候为空呢?从上面的代码可以看到,token
是通过DisplayContent的getWindowToken
方法来获取的(这些WindowToken
都保存在一个DisplayContent的HashMap<IBinder, WindowToken>实例中):它是直接调用Map的
get
方法,如果返回null的话,很可能是被移除了,搜一下"mTokenMap.remove"
,会看到在removeWindowToken
方法有调用:
顺藤摸瓜:removeWindowToken
方法何时被调用呢?emmm,现在就很清晰了:
当Activity被Destroy的时候就会调用到removeWindowToken
方法,把对应的WindowToken移除,调用remove
方法所传进去的key
(IBinder),其实就是ActivityThread调用Activity的attach
方法时传进来的,也就是Activity.mToken
了,也是这个Activity所对应的PhoneWindow里面的mAppToken
,这个Activity所对应的WindowManager,它里面也会持有Activity的PhoneWindow,当调用WindowManager的addView
方法时,还会把PhoneWindow的mAppToken
赋值给WindowManager.LayoutParams.token
!那么看回上面的那句代码:
哈哈,知道为什么会是null了吧,此时的attrs.token就是Activity的mToken,当这个Activity被Destroy之后,再通过它的WindowManager添加View,最终就会抛出BadTokenException,这也就对应了开头对这个异常的描述:" is your activity running?"。
感觉话有点多了,下面长话短说...
第二个,ADD_BAD_SUBWINDOW_TOKEN:
返回这个Flag有两种情况:第三个,ADD_NOT_APP_TOKEN:
Window类型为TYPE_APPLICATION(调用WindowManager.addView方法时不指定类型,默认就是这个),但是通过DisplayContent的getWindowToken
方法获取到的实例,不是AppWindowToken的实例(AppWindowToken是WindowToken的子类,只有是AppWindowToken才能添加View)。WindowToken何时为AppWindowToken?AppWindowToken只有在AppWindowContainerController初始化,而AppWindowContainerController是在ActivityRecord创建时初始化,看到这里基本就可以确定:如果Window类型为TYPE_APPLICATION,则必须添加在Activity上。第四个,ADD_APP_EXITING:
当AppWindowToken的removed被标记为true时就会返回这个Flag。何时被标记为true?细心的同学会发现,在前面贴出来的removeWindowToken
(Activity被Destroy时调用)方法里会调用token的setExiting
方法,这个removed
就是在这时候被标记的,调用顺序为:第五个,ADD_DUPLICATE_ADD:
看名字就知道是重复添加的意思了。除了同一个View不能添加两次,和不能同时添加两个Type为TYPE_APPLICATION_STARTING的View之外,还有一个就是,Toast窗口不能同时存在两个!第六个,ADD_MULTIPLE_SINGLETON:
SDK28的源码里,WMS的addWindow
方法不会返回这个。第七个,ADD_PERMISSION_DENIED:
很明显就是没权限。当Type指定为TYPE_PRIVATE_PRESENTATION时(私有),但是所在的显示器却不是私有的,就会返回这个Flag。终于完了。。。这是我最长的一篇回答了。
来总结一下抛出BadTokenException的原因以及解决方案:
原因一共有7个:
Activity已经Destroy了,还往里面添加View;
添加了2个Type为SUB_WINDOW的View;
使用非Activity的Context添加了Type为TYPE_APPLICATION的View;
Activity在Destroy时还往里面添加View;
同一个View添加了2次,或者,添加了2个Type为TYPE_APPLICATION_STARTING的View;
ADD_MULTIPLE_SINGLETON?不会发生这个的;
把Type为TYPE_PRIVATE_PRESENTATION的View,添加在非私有显示器上;
解决方法,就是避免以上7种做法就行了。
讲的比较到位。
补充:一些常见的场景就是:
1. 列表在“加载更多”的时候,退出了该Activity,从后台拉取数据成功时,Activity已经被Destroy了。(对应Flag: ADD_BAD_A ...查看更多
补充:一些常见的场景就是:
1. 列表在“加载更多”的时候,退出了该Activity,从后台拉取数据成功时,Activity已经被Destroy了。(对应Flag: ADD_BAD_APP_TOKEN)
2. 在Service中使用WindowManager添加悬浮窗 或者,弹出对话框时,使用的是默认的TYPE_APPLICATION类型,即没有指定View类型为SYSTEM_WINDOW。(对应Flag: ADD_NOT_APP_TOKEN)
3. 像@cscxzxzc 同学所说的那样,在Activity中showDialog,使用了Application的Context。(对应Flag: ADD_NOT_APP_TOKEN)
4. 7.1.1版本,在调用Toast的show方法之后,主线程卡住的时间 > 该Toast的显示时长。(对应Flag: ADD_BAD_APP_TOKEN)
仔细看了下,二次回复做的比较轻量,用了太多标签,确实会乱~~ 暂时修复不了啦。
哈哈,没事啦,鸿神辛苦了~
你好,能否抽点时间帮忙看看我发的评论的错误属于那种情况,从堆栈能看出属于ADD_BAD_APP_TOKEN或者ADD_BAD_SUBWINDOW_TOKEN。但是你的第一种如果是使用他的windowm ...查看更多
你好,能否抽点时间帮忙看看我发的评论的错误属于那种情况,从堆栈能看出属于ADD_BAD_APP_TOKEN或者ADD_BAD_SUBWINDOW_TOKEN。但是你的第一种如果是使用他的windowmanager去addView报错应该是没有handleResumeActivity,而是普通的addView.我这个看起来还是在启动activity的时候报错了,猜测是启动activity耗时导致token被移除,但是无法重现
来暖个场,见过在设置Dialog的时候遇到过,我记得挺清楚的(我靠希望wan android以后能定时保持一下输入的信息,刚刚放了个链接跳过去回来就没了)
1.下面这个demo中就报了这个错误2.将AlertDialog.Builder(getApplicationContext())改成AlertDialog.Builder(MainActivity.this)就好了。
我上官方文档搜了下BadTokenException发现它是个运行时异常,那解决方法就是捕捉异常并处理,或者修改代码来避免异常。它介绍说是尝试添加的View的LayoutParams.token是无效的,我看了下这个LayoutParams.token它只告诉说每一个Window对象都具有一个Token标识,一般系统会帮我们填写的。我就准备去看一看这个token是怎么来的,
在看一下 /frameworks/base/core/java/android/os/HwParcel.java
发现它是c++底层实现的返回Token
后来我查到一篇罗神写的博客,看一下大致了解了一点点,分享出来后我再去认真看一遍Android应用程序窗口(Activity)与WindowManagerService服务的连接过程分析
我这个Dialog的问题呢,我想的应该是Token越级了,一个Activity的Token,应该是不能使用Applicarion的Token的,否者是否可以实现在其他的APP中弹出一个Dialog了。
3.因为还只是个学生,开源的框架用得不多,这个就不知道了,上面的也不知道回答得对不对,不对请大佬们帮忙指正一下。
呀!第二遍忘记上传截图了,对于大家说的Toast,我原来好像见过一篇文章就说是有个开源的方案替换Toast的。现在忘了
以上大佬们说的都不错,我主要从解决方案方面入手,主要针对android.view.WindowManager$BadTokenException。
常见的,我们会经历过下面这个crash:android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@333a268 is not valid; is your activity running?
上面也分析过了,就是经过异步请求返回后显示dialog,然而当前 Activity 已经被销毁,从而抛出异常。那么如何规避呢?网上常见的方案是通过在显示弹窗前加上 Activity.isFinishing() 的判断,这做法一定是正确的吗?答案是否定的,因为如果我们没有设置任何 configChanges 属性或者固定屏幕方向一类的配置,那么我们知道,屏幕旋转后会销毁重建当前activity,但是此时 Activity.isFinishing() 方法返回的结果是 false,也就是说,当你异步返回显示弹窗前旋转了屏幕并退出当前页面,那么这层保障是无效的,必crash。解决这个问题可以有两种做法:新建了一个项目,主要用来收集日常bug和Android的疑难杂症,欢迎投出你的问题:https://github.com/Moosphan/Android-Bug-Collections/issues
感谢分享~
这个异常在需要操作Window(比如addView),但是这个Window是无效的时,就会报出来
比较常见的场景是:1.显示Dialog或者PopupWindow,但对应的Activity已经销毁时,解决方法就是显示之前判断一下,如果已被销毁就不要显示2.还是显示Dialog或者PopupWindow,但传入的Context为Application或Service等没有Window的context,且没有设置type为允许无window显示的3.Android 7.1上面因为Toast引发的BadTokenException,解决方案就是修改对应的mTN,在handler中catch该异常4.之前无聊看见的,一个比较少见的问题,在启动一个android:noHistory设置为true的activity时,如果在UI还没显示出来的时候(准确说是 ActivityThread.handleResumeActivity 之前)有地方耗时较长卡着了,这时候用户按了home键触发stop,则执行到 ActivityThread.handleResumeActivity 时,会报出这个错,原因是noHistory设置为true的activity触发stop时,会被AMS顺带着destory,client端处理超时会引发该activity被AMS强制销毁;而这个销毁的过程会把对应的token也一起销毁,当执行到handleResumeActivity时,显示ui却找不到token,就爆炸了。具体可以看这里这个异常主要发生在Android 7.x。Toast是系统层面的,不依赖前台页面,存在滥用问题。早期4.+时代做悬浮窗的时候就会把Window的类型设置为TYPE_TOAST。随着Google在Android系统版本上的不断迭代优化,终于在Android7.1.1的时候开发出了这个Bug。Android 7.1.1开始为了避免Toast被滥用,Google给Toast加上了token,这个token有个过期时间,应用在主线程处理其他任务而没能及时弹Toast,处理完任务回来弹Toast的时候,如果token过期了,WindowManager调用addView的时候就会抛出BadTokenException。解决方案可以参考Android 8.0的做法,Android 8.0在WindowManager调用addView的时候做了try-catch,这是从系统层面去解决这个问题。。。但是7.x系列只能开发者通过版本号判断,通过反射传入Handler,在dispatchMessage的时候去捕获这个异常了。
其实发展到现在,Toast的毛病挺多的。早期我们为了避免Toast一个一个排队的弹,会把Toast声明成单例的,每次只要setText然后show就行了(Weex里提供给H5的封装也是这种做法。。。然后在Oppo 9.0的机子上就出现了Toast弹过一次之后就弹不出了),但是这种做法在Android 9.0又会出弹过一个Toast之后Toast弹不出的问题...因为Android 9.0做了优化,你就算每次都调用Toast.makeText().show()也只会显示一个Toast,不会有排队弹的效果。最坑的还是关闭通知权限后弹不出Toast(小米系统做了优化,关闭通知权限也还能弹,要看到这个问题要用非小米系统的Android机)这个在api25的android手机可能会出现,toast要show但是token过期的问题。
巧了,最近看崩溃平台发现一个量大的崩溃就是BadTokenException
楼上回答的很多情况,但是看起来好想还不是我的这种。有没有大神能帮忙看看,我测试了noHistory确实可以重现一样的崩溃栈,但是我们的项目中没有设置这个flag。其他的崩溃栈就不吻合了。
跪谢token失效会导致吗
dialog 会出现。
Toast也会出现,使用这个开源方案解决:https://github.com/PureWriter/ToastCompat
不知道。挽尊