Android卡頓優化 | ANR分析與實戰(附ANR-WatchDog源碼分析及實戰、與AndroidPerformanceMonitor的區別)
- 2020 年 4 月 9 日
- 筆記
本文要點
- ANR概述
- 發生ANR後Android系統的執行流程
- ANR-WatchDog原理與實戰
- ANR的傳統解決套路
- ANR模擬實戰
- 線上ANR監控方案【ANR-WatchDog原理分析】
- ANR-WatchDog實戰
- ANR-WatchDog總結
- ANR-WatchDog與AndroidPerformanceMonitor的區別
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
- 依賴
compile 'com.github.anrwatchdog:anrwatchdog:1.4.0'
- 官網 https://github.com/SalomonBrys/ANR-WatchDog
- 原理(源碼分析):
ANRWatchDog
本身就是Thread
的子類:
ANRWatchDog
中,用一個綁定了主執行緒Looper的Handler, 去處理_ticker
【一個Runnable任務單元】; 任務單元對一些值進行了處理,如_tick
、_reported
:_tick
在初始為ANRWatchDog
的全局變數時,被賦值為0;^^^^^^^^^^^^^^^^^ 在ANRWatchDog
的run()
中, 首先被利用去判定_ticker
被post沒有(因為一開始就_tick
為0的話說明_tick
還沒被post), 沒有便將_tick
=加上卡頓周期
,之後post了_ticker
; 此時_tick
不為0!!^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^_ticker
中的run()
,再一次將_tick
置零;^^^^^^^^^^^^^^^^^^^^^^^^^^ 所以只要_ticker
不被處理,其run()
便不會執行,_tick
就不會被置零,由此根據
_tick
的值可以判斷_ticker
是否被處理了;_tick
重新歸零則主執行緒處理了_ticker
,_tick
不為零則判定主執行緒卡頓
,它沒處理_tick
!!!!!!!!ANRWatchDog
的run()
中, 用剛說的主執行緒Handler,post了_ticker
這個任務, 然後自己sleep一段時間【即一個卡頓周期,稍後細說】, 如果sleep結束之後,如果_tick != 0 && !_reported
, 則說明主執行緒還沒有處理_ticker
的run()
, 沒有處理_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監控; 兩者可以相輔相成,結合使用!