招聘網(wǎng)站建設(shè)人員的要求搜索優(yōu)化
系列文章目錄
提示:這里可以添加系列文章的所有文章的目錄,目錄需要自己手動(dòng)添加
例如:第一章 Python 機(jī)器學(xué)習(xí)入門之pandas的使用
文章目錄
- 系列文章目錄
- 前言
- ANR流程概覽
- ANR觸發(fā)機(jī)制
- 一、service超時(shí)機(jī)制
- 二、broadcast超時(shí)機(jī)制
- 三、provider超時(shí)機(jī)制
- 四、input超時(shí)機(jī)制
- ANR信息收集
- Question
前言
不論從事安卓應(yīng)用開發(fā),還是安卓系統(tǒng)研發(fā),應(yīng)該都遇到應(yīng)用無響應(yīng)(ANR,Application Not Responding)問題,當(dāng)應(yīng)用程序一段時(shí)間無法及時(shí)響應(yīng),則會(huì)彈出ANR對(duì)話框,讓用戶選擇繼續(xù)等待,還是強(qiáng)制關(guān)閉。
那么哪些場景會(huì)造成ANR呢?
Service Timeout:比如前臺(tái)服務(wù)在20s內(nèi)未執(zhí)行完成;
BroadcastQueue Timeout:比如前臺(tái)廣播在10s內(nèi)未執(zhí)行完成
ContentProvider Timeout:內(nèi)容提供者,在publish過超時(shí)10s;
InputDispatching Timeout: 輸入事件分發(fā)超時(shí)5s,包括按鍵和觸摸事件。
ANR流程概覽
我們通過一張流程圖來了解整個(gè)ANR的流程:
無論是哪種類型的ANR,哪怕是native層的ANR,最終也會(huì)通知到AnrHelper類的appNotResponding方法。所以,我們從這個(gè)方法開始了解整個(gè)ANR的流程。我們用來區(qū)分ANR的四種不同類型,其實(shí)也就是appNotResponding這個(gè)方法中的annotation不同而已,而ANR本身是不去分類型的。
- appNotResponding方法中,主要是生成AnrRecord對(duì)象,加入到mAnrRecords集合中。然后調(diào)用startAnrConsumerIfNeeded方法。
- startAnrConsumerIfNeeded方法主要是通過cas進(jìn)行判斷,避免兩個(gè)ANR線程同時(shí)執(zhí)行。如果沒有沖突的話,則開啟AnrConsumerThread線程,對(duì)mAnrRecords中的對(duì)象進(jìn)行消費(fèi)。
- AnrConsumerThread的run方法中,就是從mAnrRecords中取出對(duì)象,這些對(duì)象第一條中添加的。通過AnrRecord自身的appNotResponding方法進(jìn)行消費(fèi)。
- appNotResponding方法就是整個(gè)ANR流程的核心執(zhí)行邏輯了??偨Y(jié)一下,其實(shí)主要分為兩大塊:
- 生成ANR的相關(guān)log以及日志并打印或保存
- 觸發(fā)ANR超時(shí)機(jī)制,彈出應(yīng)用無響應(yīng)的框
ANR日志生成可查看ANR原理篇 - ANR信息收集過程
下面篇章,主要看下ANR觸發(fā)機(jī)制。
ANR觸發(fā)機(jī)制
ANR是一套監(jiān)控Android應(yīng)用響應(yīng)是否及時(shí)的機(jī)制,可以把發(fā)生ANR比作是引爆炸彈,整個(gè)流程包含三部分組成:
1.埋定時(shí)炸彈:中控系統(tǒng)(system_server進(jìn)程)啟動(dòng)倒計(jì)時(shí),在規(guī)定時(shí)間內(nèi)如果目標(biāo)(應(yīng)用進(jìn)程)沒有干完所有的活,則中控系統(tǒng)會(huì)定向炸毀(殺進(jìn)程)目標(biāo)。
2.拆炸彈:在規(guī)定的時(shí)間內(nèi)干完工地的所有活,并及時(shí)向中控系統(tǒng)報(bào)告完成,請(qǐng)求解除定時(shí)炸彈,則幸免于難。
3.引爆炸彈:中控系統(tǒng)立即封裝現(xiàn)場,抓取快照,搜集目標(biāo)執(zhí)行慢的罪證(traces),便于后續(xù)的案件偵破(調(diào)試分析),最后是炸毀目標(biāo)。
常見的ANR有service、broadcast、provider以及input。
更多細(xì)節(jié)詳見下面兩篇:
ANR原理篇 - service/broadcast/provider超時(shí)機(jī)制
ANR原理篇 - Input超時(shí)機(jī)制
一、service超時(shí)機(jī)制
下面來看看埋炸彈與拆炸彈在整個(gè)服務(wù)啟動(dòng)(startService)過程所處的環(huán)節(jié):
圖解:
- 客戶端(App進(jìn)程)向中控系統(tǒng)(system_server進(jìn)程)發(fā)起啟動(dòng)服務(wù)的請(qǐng)求
- 中控系統(tǒng)派出一名空閑的通信員(binder_1線程)接收該請(qǐng)求,緊接著向組件管家(ActivityManager線程)發(fā)送消息,埋下定時(shí)炸彈
- 通訊員1號(hào)(binder_1)通知工地(service所在進(jìn)程)的通信員準(zhǔn)備開始干活
- 通訊員3號(hào)(binder_3)收到任務(wù)后轉(zhuǎn)交給包工頭(main主線程),加入包工頭的任務(wù)隊(duì)列(MessageQueue)
- 包工頭經(jīng)過一番努力干完活(完成service啟動(dòng)的生命周期),然后等待SharedPreferences(簡稱SP)的持久化;
- 包工頭在SP執(zhí)行完成后,立刻向中控系統(tǒng)匯報(bào)工作已完成
- 中控系統(tǒng)的通訊員2號(hào)(binder_2)收到包工頭的完工匯報(bào)后,立刻拆除炸彈。如果在炸彈倒計(jì)時(shí)結(jié)束前拆除炸彈則相安無事,否則會(huì)引發(fā)爆炸(觸發(fā)ANR)
更多細(xì)節(jié)詳見startService啟動(dòng)過程分析。
二、broadcast超時(shí)機(jī)制
broadcast跟service超時(shí)機(jī)制大抵相同,如下圖所示。
圖解:
- 客戶端(App進(jìn)程)向中控系統(tǒng)(system_server進(jìn)程)發(fā)起發(fā)送廣播的請(qǐng)求
- 中控系統(tǒng)派出一名空閑的通信員(binder_1)接收該請(qǐng)求轉(zhuǎn)交給組件管家(ActivityManager線程)
- 組件管家執(zhí)行任務(wù)(processNextBroadcast方法)的過程埋下定時(shí)炸彈
- 組件管家通知工地(receiver所在進(jìn)程)的通信員準(zhǔn)備開始干活
- 通訊員3號(hào)(binder_3)收到任務(wù)后轉(zhuǎn)交給包工頭(main主線程),加入包工頭的任務(wù)隊(duì)列(MessageQueue)
- 包工頭經(jīng)過一番努力干完活(完成receiver啟動(dòng)的生命周期),然后等待SP工人完成SP數(shù)據(jù)的持久化工作,便可以向中控系統(tǒng)匯報(bào)工作完成
- 中控系統(tǒng)的通訊員2號(hào)(binder_2)收到包工頭的完工匯報(bào)后,立刻拆除炸彈。如果在倒計(jì)時(shí)結(jié)束前拆除炸彈則相安無事,否則會(huì)引發(fā)爆炸(觸發(fā)ANR)
對(duì)于靜態(tài)注冊的廣播在超時(shí)檢測過程,還多一個(gè)步驟:需要檢測SP,位于第6步和第7步之間。
SP的apply將修改的數(shù)據(jù)項(xiàng)更新到內(nèi)存,然后再異步同步數(shù)據(jù)到磁盤文件,因此很多地方會(huì)推薦在主線程調(diào)用采用apply方式,避免阻塞主線程,但靜態(tài)廣播超時(shí)檢測過程需要SP全部持久化到磁盤,如果過度使用apply會(huì)增大應(yīng)用ANR的概率,更多細(xì)節(jié)詳見系統(tǒng)SharedPreferences工作過程。
Google這樣設(shè)計(jì)的初衷是針對(duì)靜態(tài)廣播的場景下,保障進(jìn)程被殺之前一定能完成SP的數(shù)據(jù)持久化。因?yàn)樵谙蛑锌叵到y(tǒng)匯報(bào)廣播接收者工作執(zhí)行完成前,該進(jìn)程的優(yōu)先級(jí)為Foreground級(jí)別,高優(yōu)先級(jí)下進(jìn)程不但不會(huì)被殺,而且能分配到更多的CPU時(shí)間片,加速完成SP持久化。更多細(xì)節(jié)詳見Android Broadcast廣播機(jī)制。
三、provider超時(shí)機(jī)制
provider的超時(shí)是在provider進(jìn)程首次啟動(dòng)的時(shí)候才會(huì)檢測,當(dāng)provider進(jìn)程已啟動(dòng)的場景,再次請(qǐng)求provider并不會(huì)觸發(fā)provider超時(shí)。
圖解:
- 客戶端(App進(jìn)程)向中控系統(tǒng)(system_server進(jìn)程)發(fā)起獲取內(nèi)容提供者的請(qǐng)求
- 中控系統(tǒng)派出一名空閑的通信員(binder_1)接收該請(qǐng)求,檢測到內(nèi)容提供者尚未啟動(dòng),則先通過zygote孵化新進(jìn)程
- 新孵化的provider進(jìn)程向中控系統(tǒng)注冊自己的存在
- 中控系統(tǒng)的通信員2號(hào)接收到該信息后,向組件管家(ActivityManager線程)發(fā)送消息,埋下炸彈
- 通信員2號(hào)通知工地(provider進(jìn)程)的通信員準(zhǔn)備開始干活
- 通訊員4號(hào)(binder_4)收到任務(wù)后轉(zhuǎn)交給包工頭(main主線程),加入包工頭的任務(wù)隊(duì)列(MessageQueue)
- 包工頭經(jīng)過一番努力干完活(完成provider的安裝工作)后向中控系統(tǒng)匯報(bào)工作已完成
- 中控系統(tǒng)的通訊員3號(hào)(binder_3)收到包工頭的完工匯報(bào)后,立刻拆除炸彈。如果在倒計(jì)時(shí)結(jié)束前拆除炸彈則相安無事,否則會(huì)引發(fā)爆炸(觸發(fā)ANR)
更多細(xì)節(jié)詳見理解ContentProvider原理。
四、input超時(shí)機(jī)制
input的超時(shí)檢測機(jī)制跟service、broadcast、provider截然不同,
為了更好的理解input過程先來介紹兩個(gè)重要線程的相關(guān)工作:
- InputReader線程負(fù)責(zé)通過EventHub(監(jiān)聽目錄/dev/input)讀取輸入事件,一旦監(jiān)聽到輸入事件則放入到InputDispatcher的mInBoundQueue隊(duì)列,并通知其處理該事件;
- InputDispatcher線程負(fù)責(zé)將接收到的輸入事件分發(fā)給目標(biāo)應(yīng)用窗口,分發(fā)過程使用到3個(gè)事件隊(duì)列:
- mInBoundQueue用于記錄InputReader發(fā)送過來的輸入事件;
- outBoundQueue用于記錄即將分發(fā)給目標(biāo)應(yīng)用窗口的輸入事件;
- waitQueue用于記錄已分發(fā)給目標(biāo)應(yīng)用,且應(yīng)用尚未處理完成的輸入事件;
input的超時(shí)機(jī)制并非時(shí)間到了一定就會(huì)爆炸,而是處理后續(xù)上報(bào)事件的過程才會(huì)去檢測是否該爆炸,所以更像是掃雷的過程,具體如下圖所示:
圖解:
- InputReader線程通過EventHub監(jiān)聽底層上報(bào)的輸入事件,一旦收到輸入事件則將其放至mInBoundQueue隊(duì)列,并喚醒InputDispatcher線程
- InputDispatcher開始分發(fā)輸入事件,設(shè)置埋雷的起點(diǎn)時(shí)間。先檢測是否有正在處理的事件(mPendingEvent),如果沒有則取出mInBoundQueue隊(duì)頭的事件,并將其賦值給mPendingEvent,且重置ANR的timeout;否則不會(huì)從mInBoundQueue中取出事件,也不會(huì)重置timeout。然后檢查窗口是否就緒(checkWindowReadyForMoreInputLocked),滿足以下任一情況,則會(huì)進(jìn)入掃雷狀態(tài)(檢測前一個(gè)正在處理的事件是否超時(shí)),終止本輪事件分發(fā),否則繼續(xù)執(zhí)行步驟3。
- 對(duì)于按鍵類型的輸入事件,則outboundQueue或者waitQueue不為空,
- 對(duì)于非按鍵的輸入事件,則waitQueue不為空,且等待隊(duì)頭時(shí)間超時(shí)500ms
- 當(dāng)應(yīng)用窗口準(zhǔn)備就緒,則將mPendingEvent轉(zhuǎn)移到outBoundQueue隊(duì)列
- 當(dāng)outBoundQueue不為空,且應(yīng)用管道對(duì)端連接狀態(tài)正常,則將數(shù)據(jù)從outboundQueue中取出事件,放入waitQueue隊(duì)列
- InputDispatcher通過socket告知目標(biāo)應(yīng)用所在進(jìn)程可以準(zhǔn)備開始干活
- App在初始化時(shí)默認(rèn)已創(chuàng)建跟中控系統(tǒng)雙向通信的socketpair,此時(shí)App的包工頭(main線程)收到輸入事件后,會(huì)層層轉(zhuǎn)發(fā)到目標(biāo)窗口來處理
- 包工頭完成工作后,會(huì)通過socket向中控系統(tǒng)匯報(bào)工作完成,則中控系統(tǒng)會(huì)將該事件從waitQueue隊(duì)列中移除。
input超時(shí)機(jī)制為什么是掃雷,而非定時(shí)爆炸呢?
是由于對(duì)于input來說即便某次事件執(zhí)行時(shí)間超過timeout時(shí)長,只要用戶后續(xù)在沒有再生成輸入事件,則不會(huì)觸發(fā)ANR。 這里的掃雷是指當(dāng)前輸入系統(tǒng)中正在處理著某個(gè)耗時(shí)事件的前提下,后續(xù)的每一次input事件都會(huì)檢測前一個(gè)正在處理的事件是否超時(shí)(進(jìn)入掃雷狀態(tài)),檢測當(dāng)前的時(shí)間距離上次輸入事件分發(fā)時(shí)間點(diǎn)是否超過timeout時(shí)長。如果完成前一個(gè)輸入事件,則會(huì)重置ANR的timeout,從而不會(huì)爆炸。
ANR信息收集
對(duì)于service、broadcast、provider、input發(fā)生ANR后,中控系統(tǒng)會(huì)馬上去抓取現(xiàn)場的信息,用于調(diào)試分析。收集的信息包括如下:
- 將am_anr信息輸出到EventLog,也就是說ANR觸發(fā)的時(shí)間點(diǎn)最接近的就是EventLog中輸出的am_anr信息
- 收集以下重要進(jìn)程的各個(gè)線程調(diào)用棧trace信息,保存在data/anr/traces.txt文件
- 當(dāng)前發(fā)生ANR的進(jìn)程,system_server進(jìn)程以及所有persistent進(jìn)程
- audioserver, cameraserver, mediaserver, surfaceflinger等重要的native進(jìn)程
- CPU使用率排名前5的進(jìn)程
- 將發(fā)生ANR的reason以及CPU使用情況信息輸出到main log
- 將traces文件和CPU使用情況信息保存到dropbox,即data/system/dropbox目錄
- 對(duì)用戶可感知的進(jìn)程則彈出ANR對(duì)話框告知用戶,對(duì)用戶不可感知的進(jìn)程發(fā)生ANR則直接殺掉
整個(gè)ANR信息收集過程比較耗時(shí),其中抓取進(jìn)程的trace信息,每抓取一個(gè)等待200ms,可見persistent越多,等待時(shí)間越長。
關(guān)于抓取trace命令,對(duì)于Java進(jìn)程可通過在adb shell環(huán)境下執(zhí)行kill -3 [pid]可抓取相應(yīng)pid的調(diào)用棧;
對(duì)于Native進(jìn)程在adb shell環(huán)境下執(zhí)行debuggerd -b [pid]可抓取相應(yīng)pid的調(diào)用棧。
對(duì)于ANR問題發(fā)生后的蛛絲馬跡(trace)在traces.txt和dropbox目錄中保存記錄。
更多細(xì)節(jié)詳見理解Android ANR的信息收集過程
有了現(xiàn)場信息,可以調(diào)試分析,先定位發(fā)生ANR時(shí)間點(diǎn),然后查看trace信息,接著分析是否有耗時(shí)的message、binder調(diào)用,鎖的競爭,CPU資源的搶占,以及結(jié)合具體場景的上下文來分析,調(diào)試手段就需要針對(duì)前面說到的message、binder、鎖等資源從系統(tǒng)角度細(xì)化更多debug信息,這里不再展開,后續(xù)再以ANR案例來講解。
作為應(yīng)用開發(fā)者應(yīng)讓主線程盡量只做UI相關(guān)的操作,避免耗時(shí)操作,比如過度復(fù)雜的UI繪制,網(wǎng)絡(luò)操作,文件IO操作;避免主線程跟工作線程發(fā)生鎖的競爭,減少系統(tǒng)耗時(shí)binder的調(diào)用,謹(jǐn)慎使用sharePreference,注意主線程執(zhí)行provider query操作。簡而言之,盡可能減少主線程的負(fù)載,讓其空閑待命,以期可隨時(shí)響應(yīng)用戶的操作。
Question
有哪些路徑會(huì)引發(fā)ANR?
答案是從埋下定時(shí)炸彈到拆炸彈之間的任何一個(gè)或多個(gè)路徑執(zhí)行慢都會(huì)導(dǎo)致ANR(以service為例),可以是service的生命周期的回調(diào)方法(比如onStartCommand)執(zhí)行慢,可以是主線程的消息隊(duì)列存在其他耗時(shí)消息讓service回調(diào)方法遲遲得不到執(zhí)行,可以是SP操作執(zhí)行慢,可以是system_server進(jìn)程的binder線程繁忙而導(dǎo)致沒有及時(shí)收到拆炸彈的指令。另外ActivityManager線程也可能阻塞,出現(xiàn)的現(xiàn)象就是前臺(tái)服務(wù)執(zhí)行時(shí)間有可能超過10s,但并不會(huì)出現(xiàn)ANR。
發(fā)生ANR時(shí)從trace來看主線程卻處于空閑狀態(tài)或者停留在非耗時(shí)代碼的原因有哪些?
可以是抓取trace過于耗時(shí)而錯(cuò)過現(xiàn)場,可以是主線程消息隊(duì)列堆積大量消息而最后抓取快照一刻只是瞬時(shí)狀態(tài),可以是廣播的“queued-work-looper”一直在處理SP操作。
致謝:
理解Android ANR的觸發(fā)原理
http://gityuan.com/2016/07/02/android-anr/
ANR信息收集過程
http://gityuan.com/2016/12/02/app-not-response/
Intpu原理分析
http://gityuan.com/2017/01/01/input-anr/
徹底理解安卓應(yīng)用無響應(yīng)機(jī)制
http://gityuan.com/2019/04/06/android-anr/
ANR顯示和日志生成原理講解