看完這篇 Android ANR 分析佣盒,就可以和面試官裝逼了挎袜!

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)系如下圖所示:

image

<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>

  1. 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)流程如下圖所示:

image

<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è)整體流程如下圖

image

<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í)?

  1. Android輸入系統(tǒng)簡(jiǎn)介
    Android輸入系統(tǒng)總體流程與參與者如下圖所示晴裹。
image

<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ā)送和接收主要流程如圖所示:

image

<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ī)制箫荡。

  1. 輸入事件超時(shí)監(jiān)測(cè)
    按鍵事件超時(shí)監(jiān)測(cè)整體流程如下圖所示
image

<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é)
  1. 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。
  2. ANR的報(bào)告機(jī)制:分析Android如何輸出ANR日志在刺。當(dāng)ANR被發(fā)現(xiàn)后逆害,兩個(gè)很重要的日志輸出是:CPU使用情況和進(jìn)程的函數(shù)調(diào)用棧,這兩類日志是我們解決ANR問(wèn)題的利器蚣驼。
  3. 監(jiān)測(cè)ANR的核心原理是消息調(diào)度和超時(shí)處理忍燥。
    4. 只有被ANR監(jiān)測(cè)的場(chǎng)景才會(huì)有ANR報(bào)告以及ANR提示框。
  • image
image

+qq群:457848807隙姿。獲取以上高清技術(shù)思維圖,以及相關(guān)技術(shù)的免費(fèi)視頻學(xué)習(xí)資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末厂捞,一起剝皮案震驚了整個(gè)濱河市输玷,隨后出現(xiàn)的幾起案子队丝,更是在濱河造成了極大的恐慌,老刑警劉巖欲鹏,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件机久,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡赔嚎,警方通過(guò)查閱死者的電腦和手機(jī)膘盖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)尤误,“玉大人侠畔,你說(shuō)我怎么就攤上這事∷鹞睿” “怎么了软棺?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)尤勋。 經(jīng)常有香客問(wèn)我喘落,道長(zhǎng),這世上最難降的妖魔是什么最冰? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任瘦棋,我火速辦了婚禮,結(jié)果婚禮上暖哨,老公的妹妹穿的比我還像新娘赌朋。我一直安慰自己,他們只是感情好鹿蜀,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布箕慧。 她就那樣靜靜地躺著,像睡著了一般茴恰。 火紅的嫁衣襯著肌膚如雪颠焦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天往枣,我揣著相機(jī)與錄音伐庭,去河邊找鬼。 笑死分冈,一個(gè)胖子當(dāng)著我的面吹牛圾另,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播雕沉,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼集乔,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了坡椒?” 一聲冷哼從身側(cè)響起扰路,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤尤溜,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后汗唱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宫莱,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年哩罪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了授霸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡际插,死狀恐怖碘耳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情腹鹉,我是刑警寧澤藏畅,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站功咒,受9級(jí)特大地震影響愉阎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜力奋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一榜旦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧景殷,春花似錦溅呢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至绩蜻,卻和暖如春铣墨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背办绝。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工伊约, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人孕蝉。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓屡律,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親降淮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子超埋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容