最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Android BadTokenException異常解決案例詳解

 更新時(shí)間:2021年08月31日 18:11:42   作者:讓開(kāi),我要吃人了  
這篇文章主要介紹了Android BadTokenException異常詳解,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧

image.png

線上出現(xiàn)了如上的 crash,第一解決反應(yīng)是在 show dialog 之前做個(gè) isFinish 和 isDestroyed 判斷,當(dāng)我翻開(kāi)代碼正要解決時(shí),我驚了,原來(lái)已經(jīng)做過(guò)了如上的判斷檢測(cè),示例偽代碼如下:

public void showDialog(Activity activity){
    new OkHttp().call(new Callback(){
        void onSucess(Response resp){
            if(activity!=null && !activity.isFinishing() && !activity.isDestroed()){
               new Dialog().show()   
            }
        }
    })
}

這該如何是好,正常的判斷解決不了 badToken 問(wèn)題,在焦灼之際重新回顧一下 framework 的源碼,AMS 分發(fā) onDestroy 生命周期在 ActivityRecord 類(基于 Android 10 源碼):

image.png

1、第一個(gè)紅框調(diào)用 ApplicationThread binder 代理的 scheduleTransaction 方法,回執(zhí)的生命周期為 DestroyActivityItem,scheduleTransaction 方法將包裹著 DestroyActivityItem 的 ClientTransaction 分發(fā)給 ActivityThread , ActivityThread 的父類會(huì)處理 scheduleTransaction ,并將 ClientTransaction 切換到主線程進(jìn)行進(jìn)行 Activity 的生命周期調(diào)度。為什么要把這個(gè)過(guò)程理清,后面解決部分會(huì) hook 該過(guò)程

2、第二個(gè)紅框是 Destroy 生命周期超時(shí)處理,超時(shí)時(shí)間為 10s,如果分發(fā)給應(yīng)用進(jìn)程的 onDestroy 10s 內(nèi)處理未結(jié)束,AMS 也會(huì)在超時(shí)的時(shí)候,將該 Activity 標(biāo)記為已銷毀,并通知 WMS 刪除該 Activity 的 token。

通過(guò)這兩點(diǎn),我們可以推理出我們應(yīng)用當(dāng)時(shí)處于什么環(huán)境:

AMS 已經(jīng)將銷毀的指令告訴應(yīng)用進(jìn)程了,但應(yīng)用進(jìn)程一直在處理自己的事情,未處理 Destroy 生命周期(從業(yè)務(wù)代碼 > isDestroyed> = false 可知),然后 AMS 的 10s 超時(shí)機(jī)制到了,并通知 WMS 移除 token,然后我們的業(yè)務(wù)代碼異步請(qǐng)求網(wǎng)絡(luò)完成,判斷 isFinish 和 isDestroyed 都是有效的,然后就順理成章的執(zhí)行了 show dialog 操作,發(fā)生了該異常。

我們可以畫個(gè)簡(jiǎn)單的圖:

image.png

解決辦法1

既然是 AMS 發(fā)的 destroy 消息被主線程的其他任務(wù)阻塞導(dǎo)致一直沒(méi)執(zhí)行,那么,我們可以在 show dialog 的時(shí)候去檢查一下主線程的 MessageQueue,遍歷一下所有的 Message,看看里面有沒(méi)有 Destroy Message,如果有的話,說(shuō)明當(dāng)前會(huì)發(fā)生 badToken 異常。

查看了下 MessageQueue 的 mMessages 字段,發(fā)現(xiàn)該字段被標(biāo)注為 UnsupportedAppUsage 注解,看起來(lái)不支持給 app 調(diào)用,先不管,我們先 hook 一番,代碼就不貼了,后面給出示例代碼,一頓操作猛如虎,發(fā)現(xiàn)是可以通過(guò)反射拿到 Message 的,然后接下來(lái)就可以通過(guò)遞歸遍歷 Message next,取出所有的 Message。

在拿到 Message 的同時(shí),我們要怎么識(shí)別出這是個(gè) Destroy Message 呢?

這要看不同的系統(tǒng)版本:

  • Android P 之前(不包括 P),destroy message 是通過(guò)給 Message.what = DESTROY_ACTIVITY 來(lái)進(jìn)行分發(fā)的,DESTROY_ACTIVITY = 109,那么我們就可以判斷,只要 Message 中的 what 為 109 即可判斷當(dāng)前是 Destroy Message。
  • Android P 之后(包括 P),AMS 的生命周期分發(fā)改了,不再是通過(guò)調(diào)用 ApplicationThread 的某個(gè)方法,然后根據(jù) DESTROY_ACTIVITY 這種數(shù)值型來(lái)分發(fā),而是全部統(tǒng)一走 ApplicationThread 的 scheduleTransaction 方法,生命周期標(biāo)識(shí)是存放在參數(shù) ClientTransaction 中,在切換到主線程時(shí),會(huì)執(zhí)行 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 驗(yàn)證如下,destroy message 被成功拿到:

image.png

那么我們的業(yè)務(wù)代碼的判斷就可以改造成:

public void showDialog(Activity activity){
    new OkHttp().call(new Callback(){
        void onSucess(Response resp){
            if(activity!=null 
               && !activity.isFinishing() 
               && !activity.isDestroed()
                // 多加一條判斷,判斷當(dāng)前消息隊(duì)列中沒(méi)有 destroy message
               && !BadTokenUtils.isOnDestroyMsgExit()
              ){
               new Dialog().show()   
            }
        }
    })
}

這種方式有個(gè)缺點(diǎn),大量的 hook message 會(huì)造成應(yīng)用的不穩(wěn)定性。

解決方法2

業(yè)務(wù)代碼是在請(qǐng)求網(wǎng)絡(luò)成功的時(shí)候進(jìn)行的 dialog 展示,這時(shí)候又有人問(wèn)了,這是在子線程,怎么能 show dialog 呢?其實(shí)不然,ViewRoomImpl 檢驗(yàn)線程,是判斷創(chuàng)建 ViewRootImpl 時(shí)的線程與 requestLayout 的線程一致,是一樣的話,即可直接操作。

但這一點(diǎn)提醒到了我,我們能否將 show dialog 的邏輯放到主線程來(lái)做,MessageQueue 已經(jīng)有了 destroy 消息,如果我們?cè)侔l(fā)一個(gè) show dialog message 的話,那肯定是排在 destroy message 后面的(Message 會(huì)根據(jù) when 來(lái)整理鏈表),那么,先處理的 destroy message 會(huì)使 isDestroyed 為 true,這樣,我們的判斷就生效了,示例圖如下:

image.png

代碼則變?yōu)椋?/strong>

public void showDialog(Activity activity){
   new OkHttp().call(new Callback(){
       void onSucess(Response resp){
          // 先判斷一次
          if(activity!=null  && !activity.isFinishing() && !activity.isDestroed() ){ 
              // 切到主線程,post 一個(gè) message 給 MQ
              activity.runOnUiThread(new Runnable() {
                @Override
                 public void run() {
                   // 再判斷一次
            if(activity!=null  && !activity.isFinishing() && !activity.isDestroed() ){
                       new Dialog().show()   
                    }
                 }
              });
           }
    });
}

缺點(diǎn):runOnUiThread 只對(duì)異步線程有效,因?yàn)樵谥骶€程會(huì)被直接執(zhí)行,并不會(huì)插入一條 message,解決辦法也有,如果當(dāng)前是在主線程的話,可以通過(guò) handler 的方式發(fā)送一條 message,如 Handler(Looper.getMainLooper()).post()

總結(jié)

大部分場(chǎng)景都能通過(guò) isFinish 和 isDestroyed 判斷來(lái)解決,但對(duì)于主線程做耗時(shí)任務(wù)導(dǎo)致 destroy message 沒(méi)有被正確處理情況,還是得回歸到應(yīng)用穩(wěn)定性治理層面,雖然能解決 badToken 問(wèn)題,但本質(zhì)上應(yīng)用卡頓問(wèn)題依然存在.

本篇文章就到這里了,希望能夠給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!

相關(guān)文章

最新評(píng)論

阿巴嘎旗| 老河口市| 丰顺县| 乌恰县| 罗田县| 定西市| 萍乡市| 南通市| 金华市| 永善县| 淮安市| 金川县| 房产| 周口市| 介休市| 鄂州市| 永嘉县| 马关县| 铜陵市| 镇江市| 华池县| 阳春市| 仪陇县| 于都县| 观塘区| 蒙自县| 临汾市| 金湖县| 松滋市| 安化县| 茌平县| 金门县| 巩义市| 盐亭县| 灵川县| 许昌市| 嘉黎县| 伊吾县| 海安县| 长白| 益阳市|