Android卡頓優化 | ANR分析與實戰(附ANR-WatchDog源碼分析及實戰、與AndroidPerformanceMonitor的區別)

本文要點

  • ANR概述
  • 發生ANR後Android系統的執行流程
  • ANR-WatchDog原理與實戰
  • ANR的傳統解決套路
  • ANR模擬實戰
  • 線上ANR監控方案【ANR-WatchDog原理分析】
  • ANR-WatchDog實戰
  • ANR-WatchDog總結
  • ANR-WatchDog與AndroidPerformanceMonitor的區別

項目GitHub

ANR概述

  • KeyDispatchTimeout,5s 即按鍵或者觸摸事件,在特定的時間(一般5s)之內沒有響應;
  • BroadcastTimeout,前台10s,後台60s BroadReceiver 在特定的時間(一般前台10s,後台60s)之內沒有響應完成;
  • ServiceTimeout,前台20s,後台200s Service 在特定的時間(一般前台20s,後台200s)之內沒有處理完成;

發生ANR後Android系統的執行流程

  • APP發生ANR
  • 進程接收異常終止信號,開始寫入進程ANR信息(當時場景,包含當前線程所有堆棧信息、CPU/IO的使用情況等);
  • 彈出ANR提示框,提示用戶關閉APP或者繼續等待;(不同ROM表現不同,有的手機廠商會去掉這個提示框)

ANR的傳統解決套路

  • 【線下】在AS的Terminal中,使用 adb pull data/anr/traces.txt 要存儲在本地的路徑 導出上面提到的ANR現場信息文件; 導出來後,便可對文件內容進行詳細分析:從CPU、IO、鎖衝突等原因思考;

ANR模擬實戰

  • 模擬ANR原因:鎖衝突; 更改代碼:

運行程序,等到程序ANR或崩潰, 在Terminal使用剛剛提到的命令,導出ANR的信息文件:

生成文件:

打開文件,可以找到原因:

上次的BlockCanary同樣捕捉到原因:

線下套路其實就是在APP發生ANR時, 導出信息文件, 查看文件,結合代碼進行分析;

線上ANR監控方案

  • 通過FileOberver監控上述的ANR信息文件的變化, 如果這個文件發生了變化,那就說明發生了ANR, 那便可以把它上報到服務器,進行詳細的分析; 【高版本需注意權限問題】
  • ANR-WatchDog

    ANRWatchDog中,用一個綁定了主線程Looper的Handler, 去處理_ticker【一個Runnable任務單元】; 任務單元對一些值進行了處理,如_tick_reported

    _tick在初始為ANRWatchDog的全局變量時,被賦值為0;^^^^^^^^^^^^^^^^^ ANRWatchDogrun()中, 首先被利用去判定_ticker被post沒有(因為一開始就_tick為0的話說明_tick還沒被post), 沒有便將_tick=加上卡頓周期,之後post了_ticker 此時_tick不為0!!^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ _ticker中的run(),再一次將_tick置零;^^^^^^^^^^^^^^^^^^^^^^^^^^ 所以只要_ticker不被處理,其run()便不會執行, _tick就不會被置零,

    由此根據_tick的值可以判斷_ticker是否被處理了; _tick重新歸零則主線程處理了_ticker _tick不為零則判定主線程卡頓,它沒處理_tick!!!!!!!!

    ANRWatchDogrun()中, 用剛說的主線程Handler,post了_ticker這個任務, 然後自己sleep一段時間【即一個卡頓周期,稍後細說】, 如果sleep結束之後,如果_tick != 0 && !_reported 則說明主線程還沒有處理_tickerrun() 沒有處理_ticker這個任務單元, 那便認為主線程發生了卡頓【如源碼注釋所示】:!!!!!! 確定發生了卡頓,就開始封裝一個ANRError,進行後續處理了:

    另外補充一下, ANRWatchDog提供了兩個重載的構造器, 提供給開發者對卡頓判定周期進行設置,開發者不設置則使用默認配置 【跟BlockCanary同一個德行】

    接着仔細看ANRError的構造流程

    這裡是有兩種構造方式New()NewMainOnly(),其最終處理都差不多, 就是通過mainLooper拿到主線程, 再通過主線程拿到現場的堆棧信息 最後返回構造好的ANRError實例:

    拿到ANRError實例之後, 通過_anrListener.onAppNotResponding(error);回調機制處理ANRError實例;

    回調機制就妙啊!^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 剛剛的_anrListener.onAppNotResponding(error);只是一個應用層上的調用; onAppNotResponding()的實現方式暴露給開發者了, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 在外部可以通過setANRListener()自己定製包含不同處理方式的ANRListener ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    開發者不定製,則使用框架自帶的默認處理方式唄: 處理方式簡單粗暴哈,直接把ANRError丟出去, 這樣APP就直接崩潰了:

    ANRError乃是Error的子類:

ANR-WatchDog實戰

  • 引入依賴
  • 初始化ANR-WatchDog:
  • 還是上面那個項目,手動阻塞60s, 運行程序, 程序會5s後崩潰【5s是默認周期時間,崩潰操作見上面源碼分析】 logcat定位關鍵字fatal,可以看到ANRError打印的信息, 信息中包括了崩潰現場所有線程堆棧信息 以及顯示bug代碼的位置

優化: 當然默認的APP崩潰處理法並不妥當, 影響用戶體驗, 實際開發中, 我們可以自己定義ANRListener,自定義處理方式【上面說過了】, 把堆棧信息上報給服務器就是了!!!!

總結

  • 非浸入式
  • 彌補高版本無權限問題

與AndroidPerformanceMonitor的區別

  • AndroidPerformanceMonitor: 原理是基於Handler-Message機制, 監控主線程每一個Message的執行, 在每一個Message的分發執行前後,進行信息處理; (不足: 一般沒有阻塞的情況下, 每一個Message的執行時間是非常短暫的, 達不到ANR的級別; 而且InputEvent在queue.next中block,不會繼續執行dispatchMessage, 而是從native回調給InputEventReceiver.dispatchInputEvent處理分發, 所以BlockCanary也就無法監控到這類ANR)
  • ANR-WatchDog: 不管主線程是怎麼執行的, 只管最後的結果, 我sleep一個周期之後,就要看我_tick值有沒有被修改, 沒被修改就是ANR!
  • AndroidPerformanceMonitor適合全程監控卡頓, ANR-WatchDog適合補充ANR監控; 兩者可以相輔相成,結合使用!