有效治理 BadTokenException,flutter 安装 androidsdk
}
这该如何是好,正常的判断解决不了 badToken 问题,在焦灼之际重新回顾一下 framework 的源码,AMS 分发 onDestroy 生命周期在 ActivityRecord 类(基于 Android 10 源码):
1、第一个红框调用 ApplicationThread binder 代理的 scheduleTransaction 方法,回执的生命周期为 DestroyActivityItem,scheduleTransaction 方法将包裹着 DestroyActivityItem 的 ClientTransaction 分发给 ActivityThread , ActivityThread 的父类会处理 scheduleTransaction ,并将 ClientTransaction 切换到主线程进行进行 Activity 的生命周期调度。为什么要把这个过程理清,后面解决部分会 hook 该过程
2、第二个红框是 Destroy 生命周期超时处理,超时时间为 10s,如果分发给应用进程的 onDestroy 10s 内处理未结束,AMS 也会在超时的时候,将该 Activity 标记为已销毁,并通知 WMS 删除该 Activity 的 token。
通过这两点,我们可以推理出我们应用当时处于什么环境:
AMS 已经将销毁的指令告诉应用进程了,但应用进程一直在处理自己的事情,未处理 Destroy 生命周期(从业务代码 > isDestroyed> = false 可知),然后 AMS 的 10s 超时机制到了,并通知 WMS 移除 token,然后我们的业务代码异步请求网络完成,判断 isFinish 和 isDestroyed 都是有效的,然后就顺理成章的执行了 show dialog 操作,发生了该异常。
我们可以画个简单的图:
解决办法 1
既然是 AMS 发的 destroy 消息被主线程的其他任务阻塞导致一直没执行,那么,我们可以在 show dialog 的时候去检查一下主线程的 MessageQueue,遍历一下所有的 Message,看看里面有没有 Destroy Message,如果有的话,说明当前会发生 badToken 异常。
查看了下 MessageQueue 的 mMessages 字段,发现该字段被标注为 UnsupportedAppUsage
注解,看起来不支持给 app 调用,先不管,我们先 hook 一番,代码就不贴了,后面给出示例代码,一顿操作猛如虎,发现是可以通过反射拿到 Message 的,然后接下来就可以通过递归遍历 Message next,取出所有的 Message。
在拿到 Message 的同时,我们要怎么识别出这是个 Destroy Message 呢?
这要看不同的系统版本:
Android P 之前(不包括 P),destroy message 是通过给 Message.what = DESTROY_ACTIVITY 来进行分发的,DESTROY_ACTIVITY = 109,那么我们就可以判断,只要 Message 中的 what 为 109 即可判断当前是 Destroy Message。
Android P 之后(包括 P),AMS 的生命周期分发改了,不再是通过调用 ApplicationThread 的某个方法,然后根据 DESTROY_ACTIVITY 这种数值型来分发,而是全部统一走 ApplicationThread 的 scheduleTransaction 方法,生命周期标识是存放在参数 ClientTransaction 中,在切换到主线程时,会执行 ClientTransaction 的 getLifecycleStateRequest 方法,拿到 ActivityLifecycleItem,ActivityLifecycleItem 的子类很多,其中就有 DestroyActivityItem ,我们只需要判断 Message 中是否有 DestroyActivityItem 即可
部分示例代码如下:
fun isOnDestroyMsgExit(): Boolean {
val msg = hookMessage()
return nextMessage(::isOnDestroyMsgExit, msg)
}
?
private fun isOnDestroyMsgExit(msg: Message): Boolean {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) {
if (msg.what == EXECUTE_TRANSACTION && msg.obj != null) {
val clazz = msg.obj::class.java
if (TextUtils.equals(clazz.name, "android.app.servertransaction.ClientTransaction")) {
val method = clazz.getDeclaredMethod("getLifecycleStateRequest")
method.isAccessible = true
val obj = ?method.invoke(msg.obj)
if (obj!=null){
val clazzName = obj::class.java.name
if (TextUtils.equals(clazzName,"android.app.servertransaction.DestroyActivityItem") ){
return true
}
}
}
}
} else {
return msg.what == DESTROY_ACTIVITY
}
return false
}
demo 验证如下,destroy message 被成功拿到:
那么我们的业务代码的判断就可以改造成:
public void showDialog(Activity activity){
new OkHttp().call(new Callback(){
void onSucess(Response resp){
if(activity!=null
&& !activity.isFinishing()
&& !activity.isDestroed()
// 多加一条判断,判断当前消息队列中没有 destroy message
&& !BadTokenUtils.isOnDestroyMsgExit()
){
评论