1)首先,ANR(Application Not responding)是指應用程序未響應,Android系統對於一些事件須要在必定的時間範圍內完成,若是超過預約時間能未能獲得有效響應或者響應時間過長,都會形成ANR。ANR由消息處理機制保證,Android在系統層實現了一套精密的機制來發現ANR,核心原理是消息調度和超時處理。面試
2)其次,ANR機制主體實如今系統層。全部與ANR相關的消息,都會通過系統進程(system_server)調度,而後派發到應用進程完成對消息的實際處理,同時,系統進程設計了不一樣的超時限制來跟蹤消息的處理。 一旦應用程序處理消息不當,超時限制就起做用了,它收集一些系統狀態,譬如CPU/IO使用狀況、進程函數調用棧,而且報告用戶有進程無響應了(ANR對話框)。app
3)而後,ANR問題本質是一個性能問題。ANR機制實際上對應用程序主線程的限制,要求主線程在限定的時間內處理完一些最多見的操做(啓動服務、處理廣播、處理輸入), 若是處理超時,則認爲主線程已經失去了響應其餘操做的能力。主線程中的耗時操做,譬如密集CPU運算、大量IO、複雜界面佈局等,都會下降應用程序的響應能力。框架
1. 發生ANR時會調用AppNotRespondingDialog.show()方法彈出對話框提示用戶,該對話框的依次調用關係以下圖所示:ide
2. AppErrors.appNotResponding(),該方法是最終彈出ANR對話框的惟一入口,調用該方法的場景纔會有ANR提示,也能夠認爲在主線程中執行不管再耗時的任務,只要最終不調用該方法,都不會有ANR提示,也不會有ANR相關日誌及報告;經過調用關係能夠看出哪些場景會致使ANR,有如下四種場景:函數
(1)Service Timeout:Service在特定的時間內沒法處理完成oop
(2)BroadcastQueue Timeout:BroadcastReceiver在特定時間內沒法處理完成佈局
(3)ContentProvider Timeout:內容提供者執行超時性能
(4)inputDispatching Timeout: 按鍵或觸摸事件在特定時間內無響應。ui
ANR機制能夠分爲兩部分:ANR監測機制:Android對於不一樣的ANR類型(Broadcast, Service, InputEvent)都有一套監測機制。ANR報告機制:在監測到ANR之後,須要顯示ANR對話框、輸出日誌(發生ANR時的進程函數調用棧、CPU使用狀況等)。spa
整個ANR機制的代碼也是橫跨了Android的幾個層:App層:應用主線程的處理邏輯;Framework層:ANR機制的核心,主要有AMS、BroadcastQueue、ActiveServices、InputmanagerService、InputMonitor、InputChannel、ProcessCpuTracker等;Native層:InputDispatcher.cpp;
Provider超時機制遇到的比較少,暫不作分析;Broadcast目前主要想說兩個知識點:
第一:不管是普通廣播仍是有序廣播,最終廣播接受者的onreceive都是串行執行的,能夠經過Demo進行驗證;
第二:經過Demo以及框架添加相關日誌,都驗證了普通廣播也會有ANR監測機制,ANR機制以及問題分析文章認爲只有串行廣播纔有ANR監測機制,後續再會專門講解Broadcast發送及接收流程,同時也會補充Broadcast ANR監測機制;本文主要以Servie處理超時、輸入事件分發超時爲例探討ANR監測機制。
Service運行在應用程序的主線程,若是Service的執行時間超過20秒,則會引起ANR。
當發生Service ANR時,通常能夠先排查一下在Service的生命週期函數中(onCreate(), onStartCommand()等)有沒有作耗時的操做,譬如複雜的運算、IO操做等。 若是應用程序的代碼邏輯查不出問題,就須要深刻檢查當前系統的狀態:CPU的使用狀況、系統服務的狀態等,判斷當時發生ANR進程是否受到系統運行異常的影響。
如何檢測Service超時呢?Android是經過設置定時消息實現的。定時消息是由AMS的消息隊列處理的(system_server的ActivityManager線程)。 AMS有Service運行的上下文信息,因此在AMS中設置一套超時檢測機制也是合情合理的。咱們先拋出兩個問題問題一:Service啓動流程?問題一:如何監測Service超時?
主要經過以上兩個問題來講明Service監測機制,在知道Service啓動流程以後,經過Service啓動流程能夠更容易分析Service超時監測機制。
1. Service啓動流程以下圖所示:
(1)ActiveServices.realStartServiceLocked()在經過app.thread的scheduleCreateService()來建立Service對象並調用Service.onCreate()後,接着又調用sendServiceArgsLocked()方法來調用Service的其餘方法,如onStartCommand。以上兩步均是進程間通訊,應用與AMS之間跨進程通訊能夠參考應用進程與系統進程通訊
(2)以上只是列出Service啓動流程的關鍵步驟,具體每一個方法主要作哪些工做還須要查看具體的代碼,暫時先忽略這些,感興趣的能夠參考Android開發藝術探索等其餘相關資料
2. Service超時監測機制Service超時監測機制能夠從Service啓動流程中找到。
(1)ActiveServices.realStartServiceLocked()主要工做有
private final void realStartServiceLocked(ServiceRecord r, Proce***ecord app, boolean execInFg) throws RemoteException { ... // 主要是爲了設置ANR超時,能夠看出在正式啓動Service以前開始ANR監測; bumpServiceExecutingLocked(r, execInFg, "create"); // 啓動過程調用scheduleCreateService方法,最終會調用Service.onCreate方法; app.thread.scheduleCreateService(r, r.serviceInfo, // 綁定過程當中,這個方法中會調用app.thread.scheduleBindService方法 requestServiceBindingsLocked(r, execInFg); // 調動Service的其餘方法,如onStartCommand,也是IPC通信 sendServiceArgsLocked(r, execInFg, true); }
(2)bumpServiceExecutingLocked()會調用scheduleServiceTimeoutLocked()方法
void scheduleServiceTimeoutLocked(Proce***ecord proc) { if (proc.executingServices.size() == 0 || proc.thread == null) { return; } Message msg = mAm.mHandler.obtainMessage( ActivityManagerService.SERVICE_TIMEOUT_MSG); msg.obj = proc; // 在serviceDoneExecutingLocked中會remove該SERVICE_TIMEOUT_MSG消息, // 當超時後仍沒有remove SERVICE_TIMEOUT_MSG消息,則執行ActiveServices. serviceTimeout()方法; mAm.mHandler.sendMessageDelayed(msg, proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT); // 前臺進程中執行Service,SERVICE_TIMEOUT=20s;後臺進程中執行Service,SERVICE_BACKGROUND_TIMEOUT=200s }
(3)若是在指定的時間內尚未serviceDoneExecutingLocked()方法將消息remove掉,就會調用ActiveServices. serviceTimeout()方法
void serviceTimeout(Proce***ecord proc) { ... final long maxTime = now - (proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT); ... // 尋找運行超時的Service for (int i=proc.executingServices.size()-1; i>=0; i--) { ServiceRecord sr = proc.executingServices.valueAt(i); if (sr.executingStart < maxTime) { timeout = sr; break; } ... } ... // 判斷執行Service超時的進程是否在最近運行進程列表,若是不在,則忽略這個ANR if (timeout != null && mAm.mLruProcesses.contains(proc)) { anrMessage = "executing service " + timeout.shortName; } ... if (anrMessage != null) { // 當存在timeout的service,則執行appNotResponding,報告ANR mAm.appNotResponding(proc, null, null, false, anrMessage); } }
(4)Service onCreate超時監測總體流程以下圖
在onCreate生命週期開始執行前,啓動超時監測,若是在指定的時間onCreate沒有執行完畢(該該方法中執行耗時任務),就會調用ActiveServices.serviceTimeout()方法報告ANR;若是在指定的時間內onCreate執行完畢,那麼就會調用
ActivityManagerService.serviceDoneExecutingLocked()方法移除SERVICE_TIMEOUT_MSG消息,說明Service.onCreate方法沒有發生ANR,Service是由AMS調度,利用Handler和Looper,設計了一個TIMEOUT消息交由AMS線程來處理,整個超時機制的實現都是在Java層;以上就是Service超時監測的總體流程。
應用程序能夠接收輸入事件(按鍵、觸屏、軌跡球等),當5秒內沒有處理完畢時,則會引起ANR。
這裏先把問題拋出來了:輸入事件經歷了一些什麼工序才能被派發到應用的界面?如何檢測到輸入時間處理超時?
1. Android輸入系統簡介Android輸入系統整體流程與參與者以下圖所示。
簡單來講,內核將原始事件寫入到設備節點中,InputReader在其線程循環中不斷地從EventHub中抽取原始輸入事件,進行加工處理後將加工所得的事件放入InputDispatcher的派發發隊列中。InputDispatcher則在其線程循環中將派發隊列中的事件取出,查找合適的窗口,將事件寫入到窗口的事件接收管道中。
窗口事件接收線程的Looper從管道中將事件取出,交由窗口事件處理函數進行事件響應。關鍵流程有:原始輸入事件的讀取與加工;輸入事件的派發;輸入事件的發送、接收與反饋。其中輸入事件派發是指InputDispatcher不斷的從派發隊列取出事件、尋找合適的窗口進行發送的過程,輸入事件的發送是InputDispatcher經過Connection對象將事件發送給窗口的過程。
InputDispatcher與窗口之間的跨進程通訊主要經過InputChannel來完成。在InputDispatcher與窗口經過InputChannel創建鏈接以後,就能夠進行事件的發送、接收與反饋;輸入事件的發送和接收主要流程如圖所示:
其中,將輸入事件注入派發隊列後,會喚醒派發線程,派發線程循環由InputDispatcher.dispatchOnce函數完成;InputDispatcher將事件以InputMessage寫入InputChannel以後,窗口端的looper被喚醒,進而執行NativeInputReceiver::handleEvent()開始輸入事件的接收,從InputEventReceiver開始輸入事件被派發到用戶界面;以上只是輸入事件的大體流程,更詳細的流程能夠參考相關資料;在瞭解輸入系統的大體流程以後,咱們來分析輸入事件的超時監測機制。
2. 輸入事件超時監測按鍵事件超時監測總體流程以下圖所示
(1)InputDispatcher::dispatchOnceInnerLocked():
根據事件類型選擇不一樣事件的處理方法:InputDispatcher::dispatchKeyLocked()或者InputDispatcher::dispatchMotionLocked(),咱們以按鍵事件超時監測爲例進行說明;
(2)findFocusedWindowTargetsLocked()方法會調用checkWindowReadyForMoreInputLocked();該方法檢查窗口是否有能力再接收新的輸入事件;可能會有一系列的場景阻礙事件的繼續派發,相關場景有:
場景1: 窗口處於paused狀態,不能處理輸入事件「Waiting because the [targetType] window is paused.」
場景2: 窗口還未向InputDispatcher註冊,沒法將事件派發到窗口「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.」
場景3: 窗口和InputDispatcher的鏈接已經中斷,即InputChannel不能正常工做「Waiting because the [targetType] window’s input connection is [status]. The window may be in the process of being removed.」
場景4: InputChannel已經飽和,不能再處理新的事件「Waiting because the [targetType] window’s input channel is full. Outbound queue length: %d. Wait queue length: %d.」
場景5: 對於按鍵類型(KeyEvent)的輸入事件,須要等待上一個事件處理完畢「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.」
場景6: 對於觸摸類型(TouchEvent)的輸入事件,能夠當即派發到當前的窗口,由於TouchEvent都是發生在用戶當前可見的窗口。但有一種狀況, 若是當前應用因爲隊列有太多的輸入事件等待派發,致使發生了ANR,那TouchEvent事件就須要排隊等待派發。「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.」
以上這些場景就是咱們常在日誌中看到的ANR緣由的打印。
(3)其中事件分發5s限制定義在InputDispatcher.cpp;InputDispatcher::handleTargetsNotReadyLocked()方法中若是事件5s以內尚未分發完畢,則調用InputDispatcher::onANRLocked()提示用戶應用發生ANR;
//默認分發超時間爲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.若是當前沒有聚焦窗口,也沒有聚焦的應用 if (applicationHandle == NULL && windowHandle == NULL) { ... } else { // 2.有聚焦窗口或者有聚焦的應用 if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) { // 獲取等待的時間值 if (windowHandle != NULL) { // 存在聚焦窗口,DEFAULT_INPUT_DISPATCHING_TIMEOUT事件爲5s timeout = windowHandle->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT); } else if (applicationHandle != NULL) { // 存在聚焦應用,則獲取聚焦應用的分發超時時間 timeout = applicationHandle->getDispatchingTimeout( DEFAULT_INPUT_DISPATCHING_TIMEOUT); } else { // 默認的分發超時時間爲5s timeout = DEFAULT_INPUT_DISPATCHING_TIMEOUT; } } } // 若是當前時間大於輸入目標等待超時時間,即當超時5s時進入ANR處理流程 // currentTime 就是系統的當前時間,mInputTargetWaitTimeoutTime 是一個全局變量, if (currentTime >= mInputTargetWaitTimeoutTime) { // 調用ANR處理流程 onANRLocked(currentTime, applicationHandle, windowHandle, entry->eventTime, mInputTargetWaitStartTime, reason); // 返回須要等待處理 return INPUT_EVENT_INJECTION_PENDING; } }
(4)當應用主線程被卡住的事件,再點擊該應用其它組件也是無響應,由於事件派發是串行的,上一個事件不處理完畢,不會處理下一個事件。
(5)Activity.onCreate執行耗時操做,無論用戶如何操做都不會發生ANR,由於輸入事件相關監聽機制尚未創建起來;InputChannel通道尚未創建這時是不會響應輸入事件,InputDispatcher還不能事件發送到應用窗口,ANR監聽機制也尚未創建,因此此時是不會報告ANR的。
(6)輸入事件由InputDispatcher調度,待處理的輸入事件都會進入隊列中等待,設計了一個等待超時的判斷,超時機制的實如今Native層。以上就是輸入事件ANR監測機制;具體邏輯請參考相關源碼;
不管哪一種類型的ANR發生之後,最終都會調用 AppErrors.appNotResponding() 方法,所謂「異曲同工」。這個方法的職能就是向用戶或開發者報告ANR發生了。 最終的表現形式是:彈出一個對話框,告訴用戶當前某個程序無響應;輸入一大堆與ANR相關的日誌,便於開發者解決問題。
final void appNotResponding(Proce***ecord app, ActivityRecord activity, ActivityRecord parent, boolean aboveSystem, final String annotation) { ... if (ActivityManagerService.MONITOR_CPU_USAGE) { // 1. 更新CPU使用信息。ANR的第一次CPU信息採樣,採樣數據會保存在mProcessStats這個變量中 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. 打印調用棧。具體實現由dumpStackTraces()函數完成 File tracesFile = ActivityManagerService.dumpStackTraces( true, firstPids, (isSilentANR) ? null : processCpuTracker, (isSilentANR) ? null : lastPids, nativePids); String cpuInfo = null; // MONITOR_CPU_USAGE默認爲true if (ActivityManagerService.MONITOR_CPU_USAGE) { // 4. 更新CPU使用信息。ANR的第二次CPU使用信息採樣。兩次採樣的數據分別對應ANR發生先後的CPU使用狀況 mService.updateCpuStatsNow(); synchronized (mService.mProcessCpuTracker) { // 輸出ANR發生前一段時間內各個進程的CPU使用狀況 cpuInfo = mService.mProcessCpuTracker.printCurrentState(anrTime); } // 輸出CPU負載 info.append(processCpuTracker.printCurrentLoad()); info.append(cpuInfo); } // 輸出ANR發生後一段時間內各個進程的CPU使用率 info.append(processCpuTracker.printCurrentState(anrTime)); //會打印發生ANR的緣由,如輸入事件致使ANR的不一樣場景 Slog.e(TAG, info.toString()); if (tracesFile == null) { // There is no trace file, so dump (only) the alleged culprit's threads to the log // 發送signal 3(SIGNAL_QUIT)來dump棧信息 Process.sendSignal(app.pid, Process.SIGNAL_QUIT); } // 將anr信息同時輸出到DropBox mService.addErrorToDropBox("anr", app, app.processName, activity, parent, annotation, cpuInfo, tracesFile, null); // Bring up the infamous App Not Responding dialog // 5. 顯示ANR對話框。拋出SHOW_NOT_RESPONDING_MSG消息, // AMS.MainHandler會處理這條消息,顯示AppNotRespondingDialog對話框提示用戶發生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); } }
除了主體邏輯,發生ANR時還會輸出各類類別的日誌:event log:經過檢索」am_anr」關鍵字,能夠找到發生ANR的應用main log:經過檢索」ANR in 「關鍵字,能夠找到ANR的信息,日誌的上下文會包含CPU的使用狀況dropbox:經過檢索」anr」類型,能夠找到ANR的信息traces:發生ANR時,各進程的函數調用棧信息
至此ANR相關報告已經完成,後續須要分析ANR問題,分析ANR每每是從main log中的CPU使用狀況和traces中的函數調用棧開始。因此,更新CPU的使用信息updateCpuStatsNow()方法和打印函數棧dumpStackTraces()方法,是系統報告ANR問題關鍵所在,具體分析ANR問題請參考相關資料。
1. ANR的監測機制:首先分析Service和輸入事件大體工做流程,而後從Service,InputEvent兩種不一樣的ANR監測機制的源碼實現開始,分析了Android如何發現各種ANR。在啓動服務、輸入事件分發時,植入超時檢測,用於發現ANR。2. ANR的報告機制:分析Android如何輸出ANR日誌。當ANR被發現後,兩個很重要的日誌輸出是:CPU使用狀況和進程的函數調用棧,這兩類日誌是咱們解決ANR問題的利器。3. 監測ANR的核心原理是消息調度和超時處理。4. 只有被ANR監測的場景纔會有ANR報告以及ANR提示框。
ANR機制以及問題分析理解Android ANR的觸發原理深刻理解Android卷三(Android輸入系統)Android開發藝術探索Android源碼
最後,感謝本文內容所參考文章的做者。
原文連接:https://www.jianshu.com/p/ad1a84b6ec69
文章不易,若是你們喜歡這篇文章,或者對你有幫助但願你們多多,點贊,轉發,關注 哦。文章會持續更新的。絕對乾貨!!!