ANR概述
首先,ANR(Application Not responding)是指應(yīng)用程序未響應(yīng)肥惭,Android系統(tǒng)對(duì)于一些事件需要在一定的時(shí)間范圍內(nèi)完成,如果超過(guò)預(yù)定時(shí)間能未能得到有效響應(yīng)或者響應(yīng)時(shí)間過(guò)長(zhǎng)紊搪,都會(huì)造成ANR蜜葱。ANR由消息處理機(jī)制保證,Android在系統(tǒng)層實(shí)現(xiàn)了一套精密的機(jī)制來(lái)發(fā)現(xiàn)ANR耀石,核心原理是消息調(diào)度和超時(shí)處理牵囤。
其次,ANR機(jī)制主體實(shí)現(xiàn)在系統(tǒng)層滞伟。所有與ANR相關(guān)的消息揭鳞,都會(huì)經(jīng)過(guò)系統(tǒng)進(jìn)程(system_server)調(diào)度,然后派發(fā)到應(yīng)用進(jìn)程完成對(duì)消息的實(shí)際處理梆奈,同時(shí)野崇,系統(tǒng)進(jìn)程設(shè)計(jì)了不同的超時(shí)限制來(lái)跟蹤消息的處理。 一旦應(yīng)用程序處理消息不當(dāng)亩钟,超時(shí)限制就起作用了乓梨,它收集一些系統(tǒng)狀態(tài),譬如CPU/IO使用情況清酥、進(jìn)程函數(shù)調(diào)用棧扶镀,并且報(bào)告用戶有進(jìn)程無(wú)響應(yīng)了(ANR對(duì)話框)。
然后焰轻,ANR問(wèn)題本質(zhì)是一個(gè)性能問(wèn)題臭觉。ANR機(jī)制實(shí)際上對(duì)應(yīng)用程序主線程的限制,要求主線程在限定的時(shí)間內(nèi)處理完一些最常見(jiàn)的操作(啟動(dòng)服務(wù)、處理廣播蝠筑、處理輸入)狞膘, 如果處理超時(shí),則認(rèn)為主線程已經(jīng)失去了響應(yīng)其他操作的能力菱肖。主線程中的耗時(shí)操作客冈,譬如密集CPU運(yùn)算、大量IO稳强、復(fù)雜界面布局等场仲,都會(huì)降低應(yīng)用程序的響應(yīng)能力。
哪些場(chǎng)景會(huì)造成ANR退疫?
1. 發(fā)生ANR時(shí)會(huì)調(diào)用AppNotRespondingDialog.show()方法彈出對(duì)話框提示用戶渠缕,該對(duì)話框的依次調(diào)用關(guān)系如下圖所示:
<figcaption style="margin: 10px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; line-height: inherit; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;">ANR對(duì)話框調(diào)用關(guān)系.png</figcaption>
-
AppErrors.appNotResponding(),該方法是最終彈出ANR對(duì)話框的唯一入口褒繁,調(diào)用該方法的場(chǎng)景才會(huì)有ANR提示亦鳞,也可以認(rèn)為在主線程中執(zhí)行無(wú)論再耗時(shí)的任務(wù),只要最終不調(diào)用該方法棒坏,都不會(huì)有ANR提示燕差,也不會(huì)有ANR相關(guān)日志及報(bào)告;通過(guò)調(diào)用關(guān)系可以看出哪些場(chǎng)景會(huì)導(dǎo)致ANR坝冕,有以下四種場(chǎng)景:
(1)Service Timeout:Service在特定的時(shí)間內(nèi)無(wú)法處理完成
(2)BroadcastQueue Timeout:BroadcastReceiver在特定時(shí)間內(nèi)無(wú)法處理完成
(3)ContentProvider Timeout:內(nèi)容提供者執(zhí)行超時(shí)
(4)inputDispatching Timeout: 按鍵或觸摸事件在特定時(shí)間內(nèi)無(wú)響應(yīng)徒探。
ANR機(jī)制
ANR機(jī)制可以分為兩部分:
ANR監(jiān)測(cè)機(jī)制:Android對(duì)于不同的ANR類型(Broadcast, Service, InputEvent)都有一套監(jiān)測(cè)機(jī)制。
ANR報(bào)告機(jī)制:在監(jiān)測(cè)到ANR以后喂窟,需要顯示ANR對(duì)話框测暗、輸出日志(發(fā)生ANR時(shí)的進(jìn)程函數(shù)調(diào)用棧、CPU使用情況等)磨澡。
整個(gè)ANR機(jī)制的代碼也是橫跨了Android的幾個(gè)層:
App層:應(yīng)用主線程的處理邏輯碗啄;
Framework層:ANR機(jī)制的核心,主要有AMS稳摄、BroadcastQueue稚字、ActiveServices、InputmanagerService秩命、InputMonitor尉共、InputChannel、ProcessCpuTracker等弃锐;
Native層:InputDispatcher.cpp袄友;
Provider超時(shí)機(jī)制遇到的比較少,暫不做分析霹菊;Broadcast目前主要想說(shuō)兩個(gè)知識(shí)點(diǎn):
第一:無(wú)論是普通廣播還是有序廣播剧蚣,最終廣播接受者的onreceive都是串行執(zhí)行的支竹,可以通過(guò)Demo進(jìn)行驗(yàn)證;
第二:通過(guò)Demo以及框架添加相關(guān)日志鸠按,都驗(yàn)證了普通廣播也會(huì)有ANR監(jiān)測(cè)機(jī)制礼搁,ANR機(jī)制以及問(wèn)題分析文章認(rèn)為只有串行廣播才有ANR監(jiān)測(cè)機(jī)制,后續(xù)再會(huì)專門講解Broadcast發(fā)送及接收流程目尖,同時(shí)也會(huì)補(bǔ)充Broadcast ANR監(jiān)測(cè)機(jī)制馒吴;本文主要以Servie處理超時(shí)、輸入事件分發(fā)超時(shí)為例探討ANR監(jiān)測(cè)機(jī)制瑟曲。
Service超時(shí)監(jiān)測(cè)機(jī)制
Service運(yùn)行在應(yīng)用程序的主線程饮戳,如果Service的執(zhí)行時(shí)間超過(guò)20秒,則會(huì)引發(fā)ANR洞拨。
當(dāng)發(fā)生Service ANR時(shí)扯罐,一般可以先排查一下在Service的生命周期函數(shù)中(onCreate(), onStartCommand()等)有沒(méi)有做耗時(shí)的操作,譬如復(fù)雜的運(yùn)算烦衣、IO操作等歹河。 如果應(yīng)用程序的代碼邏輯查不出問(wèn)題,就需要深入檢查當(dāng)前系統(tǒng)的狀態(tài):CPU的使用情況花吟、系統(tǒng)服務(wù)的狀態(tài)等秸歧,判斷當(dāng)時(shí)發(fā)生ANR進(jìn)程是否受到系統(tǒng)運(yùn)行異常的影響。
如何檢測(cè)Service超時(shí)呢衅澈?Android是通過(guò)設(shè)置定時(shí)消息實(shí)現(xiàn)的寥茫。定時(shí)消息是由AMS的消息隊(duì)列處理的(system_server的ActivityManager線程)。 AMS有Service運(yùn)行的上下文信息矾麻,所以在AMS中設(shè)置一套超時(shí)檢測(cè)機(jī)制也是合情合理的。
我們先拋出兩個(gè)問(wèn)題
問(wèn)題一:Service啟動(dòng)流程芭梯?
問(wèn)題一:如何監(jiān)測(cè)Service超時(shí)险耀?
主要通過(guò)以上兩個(gè)問(wèn)題來(lái)說(shuō)明Service監(jiān)測(cè)機(jī)制,在知道Service啟動(dòng)流程之后玖喘,通過(guò)Service啟動(dòng)流程可以更容易分析Service超時(shí)監(jiān)測(cè)機(jī)制甩牺。
1. Service啟動(dòng)流程如下圖所示:
<figcaption style="margin: 10px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; line-height: inherit; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;">Servie啟動(dòng)流程.png</figcaption>
(1)ActiveServices.realStartServiceLocked()在通過(guò)app.thread的scheduleCreateService()來(lái)創(chuàng)建Service對(duì)象并調(diào)用Service.onCreate()后,接著又調(diào)用sendServiceArgsLocked()方法來(lái)調(diào)用Service的其他方法累奈,如onStartCommand贬派。以上兩步均是進(jìn)程間通信,應(yīng)用與AMS之間跨進(jìn)程通信可以參考應(yīng)用進(jìn)程與系統(tǒng)進(jìn)程通信
(2)以上只是列出Service啟動(dòng)流程的關(guān)鍵步驟澎媒,具體每個(gè)方法主要做哪些工作還需要查看具體的代碼搞乏,暫時(shí)先忽略這些,感興趣的可以參考Android開發(fā)藝術(shù)探索等其他相關(guān)資料
2. Service超時(shí)監(jiān)測(cè)機(jī)制
Service超時(shí)監(jiān)測(cè)機(jī)制可以從Service啟動(dòng)流程中找到戒努。
(1)ActiveServices.realStartServiceLocked()主要工作有
private final void realStartServiceLocked(ServiceRecord r,
ProcessRecord app, boolean execInFg) throws RemoteException {
...
// 主要是為了設(shè)置ANR超時(shí)请敦,可以看出在正式啟動(dòng)Service之前開始ANR監(jiān)測(cè);
bumpServiceExecutingLocked(r, execInFg, "create");
// 啟動(dòng)過(guò)程調(diào)用scheduleCreateService方法,最終會(huì)調(diào)用Service.onCreate方法;
app.thread.scheduleCreateService(r, r.serviceInfo,
// 綁定過(guò)程中侍筛,這個(gè)方法中會(huì)調(diào)用app.thread.scheduleBindService方法
requestServiceBindingsLocked(r, execInFg);
// 調(diào)動(dòng)Service的其他方法萤皂,如onStartCommand,也是IPC通訊
sendServiceArgsLocked(r, execInFg, true);
}
(2)bumpServiceExecutingLocked()會(huì)調(diào)用scheduleServiceTimeoutLocked()方法
void scheduleServiceTimeoutLocked(ProcessRecord proc) {
if (proc.executingServices.size() == 0 || proc.thread == null) {
return;
}
Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_TIMEOUT_MSG);
msg.obj = proc;
// 在serviceDoneExecutingLocked中會(huì)remove該SERVICE_TIMEOUT_MSG消息匣椰,
// 當(dāng)超時(shí)后仍沒(méi)有remove SERVICE_TIMEOUT_MSG消息裆熙,則執(zhí)行ActiveServices. serviceTimeout()方法;
mAm.mHandler.sendMessageDelayed(msg,
proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
// 前臺(tái)進(jìn)程中執(zhí)行Service禽笑,SERVICE_TIMEOUT=20s入录;后臺(tái)進(jìn)程中執(zhí)行Service温赔,SERVICE_BACKGROUND_TIMEOUT=200s
}
(3)如果在指定的時(shí)間內(nèi)還沒(méi)有serviceDoneExecutingLocked()方法將消息remove掉祈坠,就會(huì)調(diào)用ActiveServices. serviceTimeout()方法
void serviceTimeout(ProcessRecord proc) {
...
final long maxTime = now -
(proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
...
// 尋找運(yùn)行超時(shí)的Service
for (int i=proc.executingServices.size()-1; i>=0; i--) {
ServiceRecord sr = proc.executingServices.valueAt(i);
if (sr.executingStart < maxTime) {
timeout = sr;
break;
}
...
}
...
// 判斷執(zhí)行Service超時(shí)的進(jìn)程是否在最近運(yùn)行進(jìn)程列表,如果不在惋戏,則忽略這個(gè)ANR
if (timeout != null && mAm.mLruProcesses.contains(proc)) {
anrMessage = "executing service " + timeout.shortName;
}
...
if (anrMessage != null) {
// 當(dāng)存在timeout的service邀杏,則執(zhí)行appNotResponding贫奠,報(bào)告ANR
mAm.appNotResponding(proc, null, null, false, anrMessage);
}
}
(4)Service onCreate超時(shí)監(jiān)測(cè)整體流程如下圖
<figcaption style="margin: 10px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; line-height: inherit; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;">Service超時(shí)監(jiān)測(cè)整體流程.png</figcaption>
在onCreate生命周期開始執(zhí)行前,啟動(dòng)超時(shí)監(jiān)測(cè)望蜡,如果在指定的時(shí)間onCreate沒(méi)有執(zhí)行完畢(該該方法中執(zhí)行耗時(shí)任務(wù))唤崭,就會(huì)調(diào)用ActiveServices.serviceTimeout()方法報(bào)告ANR;如果在指定的時(shí)間內(nèi)onCreate執(zhí)行完畢脖律,那么就會(huì)調(diào)用ActivityManagerService.serviceDoneExecutingLocked()方法移除SERVICE_TIMEOUT_MSG消息谢肾,說(shuō)明Service.onCreate方法沒(méi)有發(fā)生ANR,Service是由AMS調(diào)度小泉,利用Handler和Looper芦疏,設(shè)計(jì)了一個(gè)TIMEOUT消息交由AMS線程來(lái)處理,整個(gè)超時(shí)機(jī)制的實(shí)現(xiàn)都是在Java層微姊;以上就是Service超時(shí)監(jiān)測(cè)的整體流程酸茴。
輸入事件超時(shí)監(jiān)測(cè)
應(yīng)用程序可以接收輸入事件(按鍵、觸屏兢交、軌跡球等)薪捍,當(dāng)5秒內(nèi)沒(méi)有處理完畢時(shí),則會(huì)引發(fā)ANR配喳。
這里先把問(wèn)題拋出來(lái)了:
輸入事件經(jīng)歷了一些什么工序才能被派發(fā)到應(yīng)用的界面酪穿?
如何檢測(cè)到輸入時(shí)間處理超時(shí)?
-
Android輸入系統(tǒng)簡(jiǎn)介
Android輸入系統(tǒng)總體流程與參與者如下圖所示晴裹。
<figcaption style="margin: 10px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; line-height: inherit; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;">輸入系統(tǒng)總體流程及參與者.png</figcaption>
簡(jiǎn)單來(lái)說(shuō)被济,內(nèi)核將原始事件寫入到設(shè)備節(jié)點(diǎn)中,InputReader在其線程循環(huán)中不斷地從EventHub中抽取原始輸入事件息拜,進(jìn)行加工處理后將加工所得的事件放入InputDispatcher的派發(fā)發(fā)隊(duì)列中溉潭。InputDispatcher則在其線程循環(huán)中將派發(fā)隊(duì)列中的事件取出净响,查找合適的窗口,將事件寫入到窗口的事件接收管道中喳瓣。窗口事件接收線程的Looper從管道中將事件取出馋贤,交由窗口事件處理函數(shù)進(jìn)行事件響應(yīng)。關(guān)鍵流程有:原始輸入事件的讀取與加工畏陕;輸入事件的派發(fā)配乓;輸入事件的發(fā)送、接收與反饋惠毁。其中輸入事件派發(fā)是指InputDispatcher不斷的從派發(fā)隊(duì)列取出事件犹芹、尋找合適的窗口進(jìn)行發(fā)送的過(guò)程,輸入事件的發(fā)送是InputDispatcher通過(guò)Connection對(duì)象將事件發(fā)送給窗口的過(guò)程鞠绰。
InputDispatcher與窗口之間的跨進(jìn)程通信主要通過(guò)InputChannel來(lái)完成腰埂。在InputDispatcher與窗口通過(guò)InputChannel建立連接之后,就可以進(jìn)行事件的發(fā)送蜈膨、接收與反饋屿笼;輸入事件的發(fā)送和接收主要流程如圖所示:
<figcaption style="margin: 10px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; line-height: inherit; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;">輸入事件發(fā)送和接收流程.png</figcaption>
其中,將輸入事件注入派發(fā)隊(duì)列后翁巍,會(huì)喚醒派發(fā)線程驴一,派發(fā)線程循環(huán)由InputDispatcher.dispatchOnce函數(shù)完成;InputDispatcher將事件以InputMessage寫入InputChannel之后灶壶,窗口端的looper被喚醒肝断,進(jìn)而執(zhí)行NativeInputReceiver::handleEvent()開始輸入事件的接收,從InputEventReceiver開始輸入事件被派發(fā)到用戶界面驰凛;以上只是輸入事件的大致流程胸懈,更詳細(xì)的流程可以參考相關(guān)資料;在了解輸入系統(tǒng)的大致流程之后恰响,我們來(lái)分析輸入事件的超時(shí)監(jiān)測(cè)機(jī)制箫荡。
-
輸入事件超時(shí)監(jiān)測(cè)
按鍵事件超時(shí)監(jiān)測(cè)整體流程如下圖所示
<figcaption style="margin: 10px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; line-height: inherit; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;">輸入事件超時(shí)監(jiān)測(cè)總體流程.png</figcaption>
(1)InputDispatcher::dispatchOnceInnerLocked():
根據(jù)事件類型選擇不同事件的處理方法:InputDispatcher::dispatchKeyLocked()或者InputDispatcher::dispatchMotionLocked(),我們以按鍵事件超時(shí)監(jiān)測(cè)為例進(jìn)行說(shuō)明渔隶;
(2)findFocusedWindowTargetsLocked()方法會(huì)調(diào)用checkWindowReadyForMoreInputLocked();該方法檢查窗口是否有能力再接收新的輸入事件;可能會(huì)有一系列的場(chǎng)景阻礙事件的繼續(xù)派發(fā)洁奈,相關(guān)場(chǎng)景有:
場(chǎng)景1: 窗口處于paused狀態(tài)间唉,不能處理輸入事件
“Waiting because the [targetType] window is paused.”
場(chǎng)景2: 窗口還未向InputDispatcher注冊(cè),無(wú)法將事件派發(fā)到窗口
“Waiting because the [targetType] window’s input channel is not registered with the input dispatcher. The window may be in the process of being removed.”
場(chǎng)景3: 窗口和InputDispatcher的連接已經(jīng)中斷利术,即InputChannel不能正常工作
“Waiting because the [targetType] window’s input connection is [status]. The window may be in the process of being removed.”
場(chǎng)景4: InputChannel已經(jīng)飽和呈野,不能再處理新的事件
“Waiting because the [targetType] window’s input channel is full. Outbound queue length: %d. Wait queue length: %d.”
場(chǎng)景5: 對(duì)于按鍵類型(KeyEvent)的輸入事件,需要等待上一個(gè)事件處理完畢
“Waiting to send key event because the [targetType] window has not finished processing all of the input events that were previously delivered to it. Outbound queue length: %d. Wait queue length: %d.”
場(chǎng)景6: 對(duì)于觸摸類型(TouchEvent)的輸入事件印叁,可以立即派發(fā)到當(dāng)前的窗口被冒,因?yàn)門ouchEvent都是發(fā)生在用戶當(dāng)前可見(jiàn)的窗口军掂。但有一種情況, 如果當(dāng)前應(yīng)用由于隊(duì)列有太多的輸入事件等待派發(fā)昨悼,導(dǎo)致發(fā)生了ANR蝗锥,那TouchEvent事件就需要排隊(duì)等待派發(fā)。
“Waiting to send non-key event because the %s window has not finished processing certain input events that were delivered to it over %0.1fms ago. Wait queue length: %d. Wait queue head age: %0.1fms.”
以上這些場(chǎng)景就是我們常在日志中看到的ANR原因的打印率触。
(3)其中事件分發(fā)5s限制定義在InputDispatcher.cpp终议;InputDispatcher::handleTargetsNotReadyLocked()方法中如果事件5s之內(nèi)還沒(méi)有分發(fā)完畢,則調(diào)用InputDispatcher::onANRLocked()提示用戶應(yīng)用發(fā)生ANR葱蝗;
//默認(rèn)分發(fā)超時(shí)間為5s
const nsecs_t DEFAULT_INPUT_DISPATCHING_TIMEOUT = 5000 * 1000000LL;
int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime,
const EventEntry* entry,
const sp<InputApplicationHandle>& applicationHandle,
const sp<InputWindowHandle>& windowHandle,
nsecs_t* nextWakeupTime, const char* reason) {
// 1.如果當(dāng)前沒(méi)有聚焦窗口穴张,也沒(méi)有聚焦的應(yīng)用
if (applicationHandle == NULL && windowHandle == NULL) {
...
} else {
// 2.有聚焦窗口或者有聚焦的應(yīng)用
if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
// 獲取等待的時(shí)間值
if (windowHandle != NULL) {
// 存在聚焦窗口,DEFAULT_INPUT_DISPATCHING_TIMEOUT事件為5s
timeout = windowHandle->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT);
} else if (applicationHandle != NULL) {
// 存在聚焦應(yīng)用两曼,則獲取聚焦應(yīng)用的分發(fā)超時(shí)時(shí)間
timeout = applicationHandle->getDispatchingTimeout(
DEFAULT_INPUT_DISPATCHING_TIMEOUT);
} else {
// 默認(rèn)的分發(fā)超時(shí)時(shí)間為5s
timeout = DEFAULT_INPUT_DISPATCHING_TIMEOUT;
}
}
}
// 如果當(dāng)前時(shí)間大于輸入目標(biāo)等待超時(shí)時(shí)間皂甘,即當(dāng)超時(shí)5s時(shí)進(jìn)入ANR處理流程
// currentTime 就是系統(tǒng)的當(dāng)前時(shí)間,mInputTargetWaitTimeoutTime 是一個(gè)全局變量,
if (currentTime >= mInputTargetWaitTimeoutTime) {
// 調(diào)用ANR處理流程
onANRLocked(currentTime, applicationHandle, windowHandle,
entry->eventTime, mInputTargetWaitStartTime, reason);
// 返回需要等待處理
return INPUT_EVENT_INJECTION_PENDING;
}
}
(4)當(dāng)應(yīng)用主線程被卡住的事件悼凑,再點(diǎn)擊該應(yīng)用其它組件也是無(wú)響應(yīng)偿枕,因?yàn)槭录砂l(fā)是串行的,上一個(gè)事件不處理完畢佛析,不會(huì)處理下一個(gè)事件益老。
(5)Activity.onCreate執(zhí)行耗時(shí)操作,不管用戶如何操作都不會(huì)發(fā)生ANR寸莫,因?yàn)檩斎胧录嚓P(guān)監(jiān)聽機(jī)制還沒(méi)有建立起來(lái)捺萌;InputChannel通道還沒(méi)有建立
這時(shí)是不會(huì)響應(yīng)輸入事件,InputDispatcher還不能事件發(fā)送到應(yīng)用窗口膘茎,ANR監(jiān)聽機(jī)制也還沒(méi)有建立桃纯,所以此時(shí)是不會(huì)報(bào)告ANR的。
(6)輸入事件由InputDispatcher調(diào)度披坏,待處理的輸入事件都會(huì)進(jìn)入隊(duì)列中等待态坦,設(shè)計(jì)了一個(gè)等待超時(shí)的判斷,超時(shí)機(jī)制的實(shí)現(xiàn)在Native層棒拂。
以上就是輸入事件ANR監(jiān)測(cè)機(jī)制伞梯;具體邏輯請(qǐng)參考相關(guān)源碼;
ANR報(bào)告機(jī)制
無(wú)論哪種類型的ANR發(fā)生以后帚屉,最終都會(huì)調(diào)用 AppErrors.appNotResponding() 方法谜诫,所謂“殊途同歸”。這個(gè)方法的職能就是向用戶或開發(fā)者報(bào)告ANR發(fā)生了攻旦。 最終的表現(xiàn)形式是:彈出一個(gè)對(duì)話框喻旷,告訴用戶當(dāng)前某個(gè)程序無(wú)響應(yīng);輸入一大堆與ANR相關(guān)的日志,便于開發(fā)者解決問(wèn)題牢屋。
final void appNotResponding(ProcessRecord app, ActivityRecord activity,
ActivityRecord parent, boolean aboveSystem, final String annotation) {
...
if (ActivityManagerService.MONITOR_CPU_USAGE) {
// 1. 更新CPU使用信息且预。ANR的第一次CPU信息采樣槽袄,采樣數(shù)據(jù)會(huì)保存在mProcessStats這個(gè)變量中
mService.updateCpuStatsNow();
}
// 記錄ANR到EventLog中
EventLog.writeEvent(EventLogTags.AM_ANR, app.userId, app.pid,
app.processName, app.info.flags, annotation);
// 輸出ANR到main log.
StringBuilder info = new StringBuilder();
info.setLength(0);
info.append("ANR in ").append(app.processName);
if (activity != null && activity.shortComponentName != null) {
info.append(" (").append(activity.shortComponentName).append(")");
}
info.append("\n");
info.append("PID: ").append(app.pid).append("\n");
if (annotation != null) {
info.append("Reason: ").append(annotation).append("\n");
}
if (parent != null && parent != activity) {
info.append("Parent: ").append(parent.shortComponentName).append("\n");
}
// 3. 打印調(diào)用棧。具體實(shí)現(xiàn)由dumpStackTraces()函數(shù)完成
File tracesFile = ActivityManagerService.dumpStackTraces(
true, firstPids,
(isSilentANR) ? null : processCpuTracker,
(isSilentANR) ? null : lastPids,
nativePids);
String cpuInfo = null;
// MONITOR_CPU_USAGE默認(rèn)為true
if (ActivityManagerService.MONITOR_CPU_USAGE) {
// 4. 更新CPU使用信息锋谐。ANR的第二次CPU使用信息采樣遍尺。兩次采樣的數(shù)據(jù)分別對(duì)應(yīng)ANR發(fā)生前后的CPU使用情況
mService.updateCpuStatsNow();
synchronized (mService.mProcessCpuTracker) {
// 輸出ANR發(fā)生前一段時(shí)間內(nèi)各個(gè)進(jìn)程的CPU使用情況
cpuInfo = mService.mProcessCpuTracker.printCurrentState(anrTime);
}
// 輸出CPU負(fù)載
info.append(processCpuTracker.printCurrentLoad());
info.append(cpuInfo);
}
// 輸出ANR發(fā)生后一段時(shí)間內(nèi)各個(gè)進(jìn)程的CPU使用率
info.append(processCpuTracker.printCurrentState(anrTime));
//會(huì)打印發(fā)生ANR的原因,如輸入事件導(dǎo)致ANR的不同場(chǎng)景
Slog.e(TAG, info.toString());
if (tracesFile == null) {
// There is no trace file, so dump (only) the alleged culprit's threads to the log
// 發(fā)送signal 3(SIGNAL_QUIT)來(lái)dump棧信息
Process.sendSignal(app.pid, Process.SIGNAL_QUIT);
}
// 將anr信息同時(shí)輸出到DropBox
mService.addErrorToDropBox("anr", app, app.processName, activity, parent, annotation,
cpuInfo, tracesFile, null);
// Bring up the infamous App Not Responding dialog
// 5. 顯示ANR對(duì)話框怀估。拋出SHOW_NOT_RESPONDING_MSG消息狮鸭,
// AMS.MainHandler會(huì)處理這條消息,顯示AppNotRespondingDialog對(duì)話框提示用戶發(fā)生ANR
Message msg = Message.obtain();
HashMap<String, Object> map = new HashMap<String, Object>();
msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG;
msg.obj = map;
msg.arg1 = aboveSystem ? 1 : 0;
map.put("app", app);
if (activity != null) {
map.put("activity", activity);
}
mService.mUiHandler.sendMessage(msg);
}
}
除了主體邏輯多搀,發(fā)生ANR時(shí)還會(huì)輸出各種類別的日志:
event log:通過(guò)檢索”am_anr”關(guān)鍵字歧蕉,可以找到發(fā)生ANR的應(yīng)用
main log:通過(guò)檢索”ANR in “關(guān)鍵字,可以找到ANR的信息康铭,日志的上下文會(huì)包含CPU的使用情況
dropbox:通過(guò)檢索”anr”類型惯退,可以找到ANR的信息
traces:發(fā)生ANR時(shí),各進(jìn)程的函數(shù)調(diào)用棧信息
至此ANR相關(guān)報(bào)告已經(jīng)完成从藤,后續(xù)需要分析ANR問(wèn)題催跪,分析ANR往往是從main log中的CPU使用情況和traces中的函數(shù)調(diào)用棧開始。所以夷野,更新CPU的使用信息updateCpuStatsNow()方法和打印函數(shù)棧dumpStackTraces()方法懊蒸,是系統(tǒng)報(bào)告ANR問(wèn)題關(guān)鍵所在,具體分析ANR問(wèn)題請(qǐng)參考相關(guān)資料悯搔。
總結(jié)
- ANR的監(jiān)測(cè)機(jī)制:首先分析Service和輸入事件大致工作流程骑丸,然后從Service,InputEvent兩種不同的ANR監(jiān)測(cè)機(jī)制的源碼實(shí)現(xiàn)開始妒貌,分析了Android如何發(fā)現(xiàn)各類ANR通危。在啟動(dòng)服務(wù)、輸入事件分發(fā)時(shí)灌曙,植入超時(shí)檢測(cè)菊碟,用于發(fā)現(xiàn)ANR。
- ANR的報(bào)告機(jī)制:分析Android如何輸出ANR日志在刺。當(dāng)ANR被發(fā)現(xiàn)后逆害,兩個(gè)很重要的日志輸出是:CPU使用情況和進(jìn)程的函數(shù)調(diào)用棧,這兩類日志是我們解決ANR問(wèn)題的利器蚣驼。
-
監(jiān)測(cè)ANR的核心原理是消息調(diào)度和超時(shí)處理忍燥。
4. 只有被ANR監(jiān)測(cè)的場(chǎng)景才會(huì)有ANR報(bào)告以及ANR提示框。