<meta charset="utf-8">
1、ANR介紹
1.1 ANR是什么
ANR,全稱為Application Not Responding,也就是應(yīng)用程序無(wú)響應(yīng)。如果 Android 應(yīng)用的界面線程處于阻塞狀態(tài)的時(shí)間過(guò)長(zhǎng),就會(huì)觸發(fā)“應(yīng)用無(wú)響應(yīng)”(ANR) 的錯(cuò)誤。
此時(shí)系統(tǒng)會(huì)向用戶顯示一個(gè)對(duì)話框,ANR 對(duì)話框會(huì)為用戶提供強(qiáng)行退出應(yīng)用的選項(xiàng)。
1.2 ANR的四種類型
在Android系統(tǒng)中,應(yīng)用程序的響應(yīng)由Activity Manager及Window Manager兩個(gè)系統(tǒng)服務(wù)所監(jiān)控。通常情況下,應(yīng)用出現(xiàn)如下四類情況時(shí),系統(tǒng)將報(bào)ANR:
- KeyDispatchTimeout(最常見(jiàn)類型)—— input事件5s內(nèi)未處理完成導(dǎo)致ANR發(fā)生,主要為按鍵和觸摸事件;
日志關(guān)鍵字:InputDispatching Timeout
- BroadcastTimeout:—— BroadcastReceiver在特定時(shí)間內(nèi)未處理完成導(dǎo)致ANR發(fā)生(限制:前臺(tái)廣播10s;后臺(tái)廣播60s);
日志關(guān)鍵字:Timeout of broadcast BroadcastRecord
- ServiceTimeout —— Service在特定的時(shí)間內(nèi)未處理完成導(dǎo)致ANR發(fā)生。(限制:前臺(tái)服務(wù)20s;后臺(tái)服務(wù)200s);
日志關(guān)鍵字:Timeout executing service
- ContentProviderTimeout —— 內(nèi)容提供者,在10s內(nèi)未處理完成導(dǎo)致ANR發(fā)生;
日志關(guān)鍵字:Timeout publishing content providers
1.3 ANR的發(fā)生原因
經(jīng)過(guò)大量ANR案例的分析,總結(jié)出以下三個(gè)ANR問(wèn)題產(chǎn)生的典型場(chǎng)景:
主線程被其他線程鎖(占比57%):調(diào)用了thread的sleep()、wait()等方法,導(dǎo)致的主線程等待超時(shí)。
系統(tǒng)資源被占用(占比14%):其他進(jìn)程系統(tǒng)資源(CPU/RAM/IO)占用率高,導(dǎo)致該進(jìn)程無(wú)法搶占到足夠的系統(tǒng)資源。
主線程耗時(shí)工作導(dǎo)致線程卡死(占比9%):例如大量的數(shù)據(jù)庫(kù)讀寫(xiě),耗時(shí)的網(wǎng)絡(luò)情況,高強(qiáng)度的硬件計(jì)算等。
2、解決ANR問(wèn)題方法論
2.1 總體思路
導(dǎo)出ANR日志信息,根據(jù)日志信息,判斷確認(rèn)發(fā)生ANR的包名類名,進(jìn)程號(hào),發(fā)生時(shí)間,導(dǎo)致ANR原因類型等。
關(guān)注系統(tǒng)資源信息,包括ANR發(fā)生前后的CPU,內(nèi)存,IO等系統(tǒng)資源的使用情況。
查看主線程狀態(tài),關(guān)注主線程是否存在耗時(shí)、死鎖、等鎖等問(wèn)題,判斷該ANR是App導(dǎo)致還是系統(tǒng)導(dǎo)致的。
結(jié)合應(yīng)用日志,代碼或源碼等,分析ANR問(wèn)題發(fā)生前,應(yīng)用是否有異常,其中具體問(wèn)題具體分析。
2.2 導(dǎo)出ANR日志
ANR問(wèn)題發(fā)生時(shí),系統(tǒng)會(huì)收集ANR相關(guān)的日志信息,CPU使用情況,trace日志也就是各線程執(zhí)行情況等信息,生成一個(gè)traces.txt的文件并且放在/data/anr/路徑下。
注意:每一次新的ANR問(wèn)題的發(fā)生,會(huì)把之前的ANR信息覆蓋掉。
我們可以通過(guò)adb命令將traces文件導(dǎo)出到本地。
adb root
adb shell ls /data/anr
adb pull /data/anr/<filename>
復(fù)制代碼
2.3 讀取關(guān)鍵日志信息
1)在log中找到ANR發(fā)生信息: Traces文件中的關(guān)鍵字,例如:
09-24 15:20:20.211 1001 1543 1570 XXXXXXX: ANR in xxxxxx
09-24 15:20:20.211 1001 1543 1570 XXXXXXX: PID: xxxxx
09-24 15:20:20.211 1001 1543 1570 XXXXXXX: Reason: xxxxxx
復(fù)制代碼
其中:
ANR in中,包括導(dǎo)致ANR的包名,類名
PID 中,為發(fā)生ANR的進(jìn)程PID
Reason 中,為導(dǎo)致ANR的原因,例如keyDispatchingTimedOut
2)找到CPU Usage信息
09-24 15:20:20.211 1001 1543 1570 XXXXXX: CPUusage from xxx to xxx ago xxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxx
09-24 15:20:20.211 1001 1543 1570 XXXXXX: CPUusage from xxx to xxx later xxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxx
復(fù)制代碼
其中
ago 表示ANR發(fā)生前的CPU的使用情況
later表示ANR發(fā)生后的CPU的使用情況
重點(diǎn)關(guān)注xxx%TOTAL: xxx% user + xxx% kernel + xxx% iowait,可通過(guò)這幾項(xiàng)了解到CPU的占用情況。
2.4 具體分析
分析CPU usage以后,如若還是無(wú)法找出問(wèn)題原因,則需要進(jìn)一步分析trace文件。traces文件中詳細(xì)記錄了發(fā)生ANR前后該進(jìn)程的各個(gè)線程的Stack,一般從主線程的stack入手分析,查看分析ANR問(wèn)題發(fā)生前,應(yīng)用是否有異常。
其中不同場(chǎng)景下的ANR問(wèn)題情況不大相同,需要具體情況具體分析,此處就不展開(kāi)詳細(xì)描述。
3、ANR問(wèn)題難點(diǎn)及破題思路
3.1 ANR難點(diǎn)
用戶在應(yīng)用內(nèi)的絕大部分操作,比如按鈕點(diǎn)擊,加載資源,頁(yè)面跳轉(zhuǎn)等操作,都需要有App的主動(dòng)反饋,但ANR發(fā)生時(shí),在用戶等待數(shù)秒后,僅會(huì)彈出一個(gè)“應(yīng)用無(wú)響應(yīng)”的彈窗給用戶,這會(huì)給用戶帶來(lái)“應(yīng)用難用”的感覺(jué),極其影響用戶體驗(yàn)。
但是,現(xiàn)網(wǎng)中的ANR問(wèn)題又很難處理,問(wèn)題包括但不限于:
平時(shí)的測(cè)試難以覆蓋,畢竟ANR經(jīng)常出現(xiàn)在老設(shè)備、弱網(wǎng)絡(luò)環(huán)境的場(chǎng)景下,測(cè)試難以做到全場(chǎng)景覆蓋。
對(duì)于現(xiàn)網(wǎng)應(yīng)用的ANR問(wèn)題,如果問(wèn)題非必現(xiàn),則定位難度較高,需要有可以復(fù)現(xiàn)問(wèn)題的實(shí)際設(shè)備在身邊,才能獲取到具體日志trace等信息。
ANR問(wèn)題定位復(fù)雜,影響因素多,一些新負(fù)責(zé)定位ANR問(wèn)題的同學(xué),上手困難,問(wèn)題解決比較依賴經(jīng)驗(yàn)。
3.2 ANR處理新方案
除了依賴現(xiàn)有傳統(tǒng)的ANR問(wèn)題定位經(jīng)驗(yàn),配合第三方應(yīng)用監(jiān)控平臺(tái)、進(jìn)行ANR問(wèn)題的處理,也是方便快捷的ANR處理手段。
提升用戶體驗(yàn)迫在眉睫,但ANR問(wèn)題對(duì)用戶體驗(yàn)影響大, 定位解決ANR問(wèn)題老大難,針對(duì)這個(gè)需求痛點(diǎn),越來(lái)愈多的第三方開(kāi)始研究并對(duì)外提供應(yīng)用性能監(jiān)控工具。
性能管理(App Performance Management,簡(jiǎn)稱APM)是華為AppGallery Connect質(zhì)量系列服務(wù)中的其中一項(xiàng),提供分鐘級(jí)應(yīng)用性能監(jiān)控能力,其ANR分析功能,更是解決ANR問(wèn)題定位與處理的最佳搭檔。使用AGC性能管理服務(wù)監(jiān)控應(yīng)用ANR,能夠?yàn)槟鷰?lái)以下好處:
1.實(shí)時(shí)監(jiān)控現(xiàn)網(wǎng)應(yīng)用ANR,現(xiàn)網(wǎng)應(yīng)用ANR趨勢(shì)全掌握。
2.ANR現(xiàn)場(chǎng)信息自動(dòng)采集和展示,大部分情況無(wú)需復(fù)現(xiàn),在線定位問(wèn)題。
3.通過(guò)APM頁(yè)面,定位思路系統(tǒng)化,快速上手ANR問(wèn)題定位,及時(shí)解決問(wèn)題。
4、ANR問(wèn)題解決案例整理
接下來(lái)以華為AGC性能管理服務(wù)為例,介紹配合AGC性能管理服務(wù),如何快速定位典型的ANR問(wèn)題。
4.1 案例(一):死鎖導(dǎo)致的ANR問(wèn)題定位
4.1.1 發(fā)現(xiàn)問(wèn)題 在華為AGC控制臺(tái)的我的項(xiàng)目-質(zhì)量-性能管理頁(yè)面,在“ANR分析”頁(yè)簽下,發(fā)現(xiàn)排在第一位的“用戶ANR率”高達(dá)16.67%,決定優(yōu)先解決該類ANR問(wèn)題。
4.1.2 定位問(wèn)題 點(diǎn)開(kāi)TOP排行榜中該類問(wèn)題卡片,進(jìn)入了該類“ANR問(wèn)題詳情”頁(yè)面,進(jìn)一步查看分析該ANR 問(wèn)題的數(shù)據(jù)報(bào)告。
在這個(gè)“ANR問(wèn)題詳情”頁(yè)面中,分析用戶數(shù)分布餅圖,發(fā)現(xiàn)該類ANR問(wèn)題在“應(yīng)用版本2.0”、“手機(jī)型號(hào)HUAWEI VOG-AL10”、“系統(tǒng)版本10”這三個(gè)條件下,ANR影響的用戶數(shù)最多。
在報(bào)告下方的“發(fā)生記錄”中,找到滿足這三個(gè)條件的發(fā)生記錄,點(diǎn)擊“查看詳情”準(zhǔn)備針對(duì)具體的問(wèn)題進(jìn)行分析。
(1) 分析系統(tǒng)資源狀態(tài) 首先,通過(guò)報(bào)告,發(fā)現(xiàn)該問(wèn)題發(fā)生時(shí),CPU占用是20%、IO占用是0%、未發(fā)生過(guò)低內(nèi)存、應(yīng)用被分配堆是26.50MB、應(yīng)用已用堆是8.69MB,線程數(shù)是61,從系統(tǒng)資源來(lái)看,未出現(xiàn)明顯的異常,如下圖所示:
因?yàn)锳NR問(wèn)題原因可以分為兩大類,一是系統(tǒng)資源不足導(dǎo)致,二是自身代碼邏輯導(dǎo)致,綜合以上系統(tǒng)資源信息,該ANR問(wèn)題不是由于系統(tǒng)資源不足導(dǎo)致,那么分析該ANR問(wèn)題思路轉(zhuǎn)變?yōu)椋涸揂NR問(wèn)題由自身代碼邏輯導(dǎo)致,接下來(lái),我們順著該思路分析這次的ANR問(wèn)題。 (2) 查看主線程狀態(tài):發(fā)現(xiàn)ANR代碼片段 自身代碼邏輯導(dǎo)致ANR問(wèn)題,其主要分析思路是查看主線程堆棧及線程狀態(tài),我們?cè)谛阅芄芾眄?yè)面上“主線程堆棧”頁(yè)簽中能夠找到問(wèn)題堆棧,發(fā)現(xiàn)該問(wèn)題發(fā)生時(shí),主線程處于獲取鎖狀態(tài),到此我們能夠得出結(jié)論:該ANR問(wèn)題是因?yàn)橹骶€程一直在等待鎖資源,而被阻塞,導(dǎo)致了后續(xù)輸入事件未被響應(yīng),從而觸發(fā)了應(yīng)用的“Input dispatching timed out”類型的ANR。
查看具體的堆棧信息,我們找到了ANR問(wèn)題代碼片段,發(fā)現(xiàn)死鎖是發(fā)生在“com.aiops.hiperformance.MainActivity.dispatchActivityDestroyed”調(diào)用中。查看代碼發(fā)現(xiàn),死鎖發(fā)生在“mLock.readLock().lock()”函數(shù)中。
通過(guò)在代碼中搜索mLock加鎖代碼的調(diào)用,發(fā)現(xiàn)了僅在MainActivity文件中,才會(huì)存在“mLock.readLock.lock()”代碼, 由此判斷,異常代碼僅存在于MainActivity中,因此我們縮小了問(wèn)題代碼范圍。 在正在的代碼編寫(xiě)過(guò)程中,鎖的申請(qǐng)與釋放已經(jīng)成為一種編碼習(xí)慣,如果鎖未釋放,可能是在釋放鎖之前,出現(xiàn)了某種我們編碼未考慮的異常,導(dǎo)致鎖未釋放或釋放失敗。 由此分析,我們接下來(lái)嘗試使用“找到ANR問(wèn)題發(fā)生之前,應(yīng)用是否有異常發(fā)生”的思路,繼續(xù)分析。
我們先找到申請(qǐng)鎖動(dòng)作開(kāi)始時(shí)間點(diǎn),由阻塞動(dòng)作開(kāi)始時(shí)間點(diǎn)往前分析,尋找異常信息。我們切換到“ANR信息”頁(yè)簽, 發(fā)現(xiàn)主執(zhí)行隊(duì)列首元素在5.5s前已經(jīng)存在,ANR發(fā)生時(shí)間是“2020-09-27 09:48:27”, 因此我們可計(jì)算出獲取鎖動(dòng)作大概是在“2020-09-27 09:48:21”發(fā)生。
(3) 查看應(yīng)用日志 接下來(lái),我們把頁(yè)簽切到“系統(tǒng)日志”中,我們目前知道鎖獲取動(dòng)作在“2020-09-27 09:48:21”左右發(fā)生。我們接下來(lái)僅需要在日志中,從該時(shí)間點(diǎn)往前分析,看是否由相關(guān)異常,是導(dǎo)致該鎖未被釋放的關(guān)鍵因素。
我們發(fā)現(xiàn)在“09:48:18.365”時(shí)系統(tǒng)拋出了“OutofBoundsException”異常,并且打印了異常堆棧,我們發(fā)現(xiàn),該異常就出現(xiàn)在MainActivity,也就是我們之前的問(wèn)題代碼范圍中,我們通過(guò)該堆棧,找到了異常代碼。
發(fā)現(xiàn)在“getShareDataInterceptor”調(diào)用時(shí),拋出了“越界異常”,導(dǎo)致了“mLock.readLock”未被釋放,由此我們已經(jīng)知道導(dǎo)致該ANR問(wèn)題的具體原因:異常場(chǎng)景導(dǎo)致鎖資源未被釋放,從而造成了主線程出現(xiàn)死鎖。
4.1.3 解決問(wèn)題 為了修復(fù)了該問(wèn)題,我們做了以下措施,解決該問(wèn)題的同時(shí),預(yù)防同類問(wèn)題發(fā)生:
分析異常具體原因并修改代碼,防止越界異常再次出現(xiàn)。
捕獲該異常,保護(hù)代碼在資源釋放前被異常拋出。
排查其他代碼,在資源釋放前,加上保護(hù),保證資源及時(shí)釋放。
4.2 案例(二):IO資源不足導(dǎo)致ANR問(wèn)題定位
4.2.1 定位問(wèn)題 直奔問(wèn)題核心,直接進(jìn)入“單次ANR問(wèn)題” 頁(yè)面,去分析問(wèn)題,強(qiáng)化我們借助性能管理服務(wù)定位ANR問(wèn)題思路。
(1)分析系統(tǒng)資源狀態(tài). 首先,通過(guò)報(bào)告,發(fā)現(xiàn)該問(wèn)題發(fā)生時(shí),CPU占用是100%、IO占用是84%、未發(fā)生過(guò)低內(nèi)存、應(yīng)用被分配堆是26.50MB、應(yīng)用已用堆是8.69MB,從系統(tǒng)資源來(lái)看,CPU占用和IO占用出現(xiàn)明顯異常,如下圖所示:
由定位大部分ANR問(wèn)題經(jīng)驗(yàn)可知,該ANR問(wèn)題是由于系統(tǒng)資源不足導(dǎo)致,那么分析該ANR問(wèn)題思路為:找到自身應(yīng)用程序ANR代碼片段,分析否能夠優(yōu)化代碼,在高IO情況下,不觸發(fā)ANR。
(2)查看主線程狀態(tài):發(fā)現(xiàn)問(wèn)題原因 我們切換到“主線程堆棧”頁(yè)簽,觀察主線程代碼。
通過(guò)觀察主線程堆棧,我們發(fā)現(xiàn)了一個(gè)存在問(wèn)題的地方,主線程里面直接在做數(shù)據(jù)庫(kù)操作,在系統(tǒng)IO高的情況,此操作必定會(huì)導(dǎo)致主線程被阻塞。我們通過(guò)堆棧找到對(duì)應(yīng)的代碼。
由此我們確認(rèn),在代碼中存在訪問(wèn)SQLite的操作。這時(shí)候有經(jīng)驗(yàn)的開(kāi)發(fā)者已經(jīng)知道,問(wèn)題能夠通過(guò)優(yōu)化解決,僅需要將該IO操作放在線程中執(zhí)行即可。
(3) 查看應(yīng)用日志 已經(jīng)在上一環(huán)節(jié)分析出ANR原因,無(wú)需此步驟。
4.2.2 解決問(wèn)題 我們做了以下措施,優(yōu)化了該問(wèn)題代碼,預(yù)防ANR問(wèn)題發(fā)生。
4.3 案例(三):主線程死循環(huán)導(dǎo)致ANR問(wèn)題定位 4.3.1 定位問(wèn)題 話不多說(shuō),直接到“單次ANR問(wèn)題”,固化問(wèn)題定位思路。
(1)首先,通過(guò)報(bào)告,發(fā)現(xiàn)該問(wèn)題發(fā)生時(shí),CPU占用是25%、IO占用是0%、未發(fā)生過(guò)低內(nèi)存、應(yīng)用被分配堆是18.01MB、應(yīng)用已用堆是8.08MB,線程數(shù)是43,從系統(tǒng)資源來(lái)看,均未出現(xiàn)明顯異常,如下圖所示:
由定位大部分ANR問(wèn)題經(jīng)驗(yàn)可知,該ANR問(wèn)題大概率不是由于系統(tǒng)資源不足導(dǎo)致,那么分析該ANR問(wèn)題思路轉(zhuǎn)變?yōu)椋涸揂NR問(wèn)題由自身代碼邏輯導(dǎo)致,接下來(lái),我們順著該思路分析這次的ANR問(wèn)題。
(2)查看主線程狀態(tài):發(fā)現(xiàn)問(wèn)題原因 自身代碼邏輯導(dǎo)致ANR問(wèn)題,其主要分析思路是查看主線程堆棧及線程狀態(tài),我們?cè)谛阅芄芾眄?yè)面上“主線程堆棧”頁(yè)簽中能夠找到問(wèn)題堆棧。
發(fā)現(xiàn)該問(wèn)題發(fā)生時(shí),發(fā)現(xiàn)主線程堆棧在getActivity中被阻塞,主線程處于“SUSPENDED”狀態(tài)。這時(shí)我們通過(guò)堆棧,找到問(wèn)題代碼。
通過(guò)代碼分析,懷疑主線程在該處出現(xiàn)死循環(huán)。我們知道如果應(yīng)用程序出現(xiàn)死循環(huán)會(huì)導(dǎo)致應(yīng)用程序的CPU用戶態(tài)時(shí)間占用異常升高,我們知道“ANR信息”頁(yè)簽中記錄了ANR發(fā)生時(shí)的各進(jìn)程的CPU占用信息,于是我們?cè)陧?yè)面上切換到“ANR信息”頁(yè)簽。
我們?cè)凇癆NR信息”頁(yè)簽中發(fā)現(xiàn),自身應(yīng)用程序CPU用戶態(tài)的資源占用達(dá)到了94%,因此驗(yàn)證了我們之前的猜想:主線程出現(xiàn)了死循環(huán),導(dǎo)致了ANR問(wèn)題。
(3)查看應(yīng)用日志 已經(jīng)在上一環(huán)節(jié)分析出ANR原因,無(wú)需此步驟。
4.3.2 解決問(wèn)題 我們做了以下措施,優(yōu)化了該問(wèn)題代碼,預(yù)防ANR問(wèn)題發(fā)生。
5、案例總結(jié)
以上ANR問(wèn)題的解決與處理,都是配合華為AppGallery Connect性能管理管理服務(wù)完成的,其中的ANR問(wèn)題分析報(bào)告,ANR問(wèn)題發(fā)生時(shí)的問(wèn)題記錄,都由AGC性能管理服務(wù)界面所提供。
通過(guò)AGC性能服務(wù)里的 ANR分析詳情 可以查看發(fā)生某類ANR問(wèn)題時(shí)的趨勢(shì)及分布信息,其中包括按應(yīng)用版本版本分布,按手機(jī)型號(hào)分布,按系統(tǒng)版本分布和問(wèn)題發(fā)生的實(shí)時(shí)走勢(shì)。幫助分析這一類ANR問(wèn)題對(duì)用戶的影響趨勢(shì),以及問(wèn)題復(fù)現(xiàn)條件。
另外開(kāi)發(fā)者可以通過(guò)詳細(xì)的問(wèn)題發(fā)生記錄,獲取到該問(wèn)題發(fā)生時(shí)更加詳細(xì)的設(shè)備信息,系統(tǒng)信息,應(yīng)用信息和堆棧日志,幫助開(kāi)發(fā)者快速定位該問(wèn)題。
6、相關(guān)鏈接
AGC性能管理服務(wù)的超簡(jiǎn)單集成,可以參考該文章:developer.huawei.com/consumer/cn…
AGC性能管理服務(wù)官方文檔,可參考:developer.huawei.com/consumer/cn…