最近微信安卓版的“兩位數字+15個中文字符句號”BUG把ANR帶回了大家的視野。
前情介紹-微信bug事件
在微信上給安卓手機用戶發送:
“11。。。。。。。。。。。。。。。”
(兩位數字+15個中文字符句號)接收到這樣的信息以后,部分安卓手機在發送或收到這條消息時微信會無響應,如下圖。
本文將從如下幾個方面給大家介紹一下Android ANR相關的知識:
ANR的原理
如何定位分析ANR(以微信為例)
如何解決&避免ANR
ANR的原理
什么是ANR
ANR全稱Application Not Responding,意為程序未響應。Android基於消息處理機制系統對於一些事件需要在一定的時間范圍內完成,如果超過預定時間能未能得到有效響應就會造成ANR。發生ANR時的表現一般是彈出一個提示框,告知程序無響應,讓用戶選擇等待還是強制退出。時的表現一般是彈出一個提示框,告知程序無響應,讓用戶選擇等待還是強制退出。
什么場景會發生ANR
一般情況下,如下場景會導致ANR:
Service Timeout:比如前台服務在20s內未執行完成
BroadcastReceiver Timeout:比如前台廣播在10s內未執行完成
ContentProvider Timeout:內容提供者,在publish過超時10s
InputDispatching Timeout: 輸入事件分發超時5s,包括按鍵和觸摸事件
系統如何發現ANR
Android基於消息處理機制在系統層實現了一套發現ANR的機制,其其核心原理是消息調度和超時處理。
下面以Service為例簡單介紹下ANR的檢測流程。
Service的ANR機制
Service的ANR檢測流程可以簡單歸納為首先埋一個超時消息,然后操作在超時內完成之后移除超時消息,否則消息被系統收到觸發ANR。
Service的ANR消息埋點
Service的啟動的大致過程如下圖:
在Service進程啟動之后並attach到system_server進程時會進行ANR埋點,代碼如下:
該方法的主要工作就是發送delay消息(SERVICE_TIMEOUT_MSG),所以,如果我們不能在操作完成時remove該消息,則消息會被發送到MainHandler從而觸發ANR。
Service的ANR消息移除
根據ANR對監測原理,既然埋下了超時消息,那必然也有對應對移除邏輯,對於Service,在Service啟動完成時會對消息進行移除,代碼如下:
可以看出該方法的主要工作就是在service啟動完成時則移除服務超時消息。
觸發ANR
當然,如果沒能及時remove消息,則會觸發ANR,通過上述代碼,我們可以發現在system_server進程中有一個Handler線程, 名叫”ActivityManager”,而ANR對消息也是向該Handler線程發送。該Handler對ANR對處理邏輯如下:
總結
通過如上分析,可以大致總結初Android對ANR對監測機制主要基於消息機制,通過預發送超時消息並及時移除來實現超時檢測對功能。
ANR的信息收集
通過如上的ANR觸發邏輯可以看到ANR觸發時最終都會調用AMS.AppNotResponding()方法,下面從這個方法說起。
當發生ANR時, 會按順序依次執行:
-
第一次更新CPU的統計信息。這是發生ANR時,第一次CPU使用信息的采樣
-
輸出ANR Reason信息到EventLog, 也就是說ANR觸發的時間點最接近的就是EventLog中輸出的am_anr信息
-
填充firstPids和lastPids數組
-
收集並輸出重要進程列表中的各個線程的traces信息
-
第二次更新CPU統計信息,跟第一次一樣,兩次采樣的數據分別對應ANR發生前后的CPU使用情況
-
將traces文件和CPU使用情況信息保存到dropbox,即data/system/dropbox目錄
-
根據進程類型,來決定直接后台殺掉,還是彈框告知用戶
可以看出,除了前端提示之外,ANR發生的時候會寫各種類型的日志來方便后續排查問題,主要的日志包括如下:
-
event log,主要包括ANR的發生時間,類型等基本信息
-
main log,主要包括ANR發生時的詳細信息,如CPU的使用情況等
-
dropbox日志,通過dumpsys dropbox找到,主要為ANR發生時的詳細信息,trace、函數調用、CPU信息等
-
trace日志,最常用的ANR分析文件,主要包括CPU信息和各進程的函數調用棧信息
如何定位和分析ANR的問題
通過對ANR的原理了解,我們可以從多個角度對ANR進行問題分析,如通過分析trace日志,dropbox日志等。
通過logcat分析
簡單的情景,我們可以直接在logcat中檢索ANR in關鍵詞即可找到對應的ANR原因、堆棧信息和CPU使用情況,格式大致如下:
通過日志能看到ANR的原因(Input dispatching timed out),發生時的CPU負載等信息。
分析traces文件
實際情況中,這些信息相對還是偏少的,所以我們就需要通過分析trace文件或者dropbox文件來獲取詳細信息來定位問題。下面以微信為例:
首先,我們拿到trace文件,一般情況下,trace文件在如下目錄:
但是實際中,部分機型會對trace有rename的過程,所以trace文件的存儲可能如下:
如圖,trace文件的格式為traces_[package]_[time].txt
將trace文件pull到本地,打開后可以看到如下的內容:
有源碼場景下問題定位
一般情況下,如果是自有的App,我們直接通過分析堆棧信息,即可定位到具體的代碼行然后可以直接通過源碼調試解決問題。
無源碼場景下問題定位
如果App是黑盒,比如微信這樣,那我們就需要借助一些反編譯的手段來進行定位。
以微信的這個ANR為例,通過分析多個ANR的trace文件后,發現每次的堆棧其實都有細微的不一樣,上面的這個堆棧信息里面顯示的錯誤可能是由於正則匹配,但是還有如下的情況:
通過這個堆棧信息可以看出是由於獲取textWidth導致的問題,所以可以通過找到相同的父調用鏈來分析問題:
所以我們基本可以定位到問題是在celltextview到這幾個方法調用里面。
然后,我們就可以開始進行反編譯定位問題了,一般我們有兩種方案:
-
通過動態調試來找到問題。首先,我們需要co.debuggable = 1的設備(root或者模擬器均可),然后先通過baksmali將微信apk反編譯成smali文件,然后通過在android studio安裝SmaliIDEA調試插件,導入反編譯好的smali項目,即可開始調試(具體操作可以自行查閱)。
-
直接反編譯代碼,然后人肉定位問題。首先,我們需要先將apk解壓,取出dex文件,借助dex2jar將dex文件轉換成jar文件,然后借助jd-gui來查看源碼,然后人肉定位問題。
通過如上的兩個方案,可以幫助我們在黑盒的情況下定位到具體問題。
如何避免ANR
ANR本質是一個性能問題,要求主線程在超時內完成操作,所以想要避免ANR,根本的就是需要杜絕主線程中的耗時操作。
所以我們可以使用一些異步的方式來進行耗時操作,常用的有AsynkTask、Thread等,然后通過Handler來處理結果,只在主程序進行耗時較少的更新操作。