写点什么

有效治理 BadTokenException,flutter 安装 androidsdk

用户头像
Android架构
关注
发布于: 19 小时前

}


这该如何是好,正常的判断解决不了 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 操作,发生了该异常。


我们可以画个简单的图:


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


解决办法 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()


){

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
有效治理 BadTokenException,flutter安装androidsdk