[TOC]
## 簡介
ANR(Application Not Responding),應用程序無響應,簡單一個定義,卻涵蓋了很多Android系統的設計思想。
ANR由消息處理機制保證,Android在系統層實現了一套精密的機制來發現ANR,核心原理是 **消息調度** 和 **超時處理** 。
造成ANR的場景有
* Service Timeout:比如前臺服務在20s內未執行完成;
* BroadcastQueue Timeout:比如前臺廣播在10s內未執行完成
* ContentProvider Timeout:內容提供者,在publish過超時10s;
* InputDispatching Timeout: 輸入事件分發超時5s,包括按鍵和觸摸事件。
## ANR監測機制
ANR監測機制包含三種:
* **Service ANR**,前臺進程中Service生命周期不能超過20秒,后臺進程中Service的生命周期不能超過200秒。 在啟動Service時,拋出定時消息SERVICE\_TIMEOUT\_MSG或SERVICE\_BACKGOURND\_TIMEOUT\_MSG,如果定時消息響應了,則說明發生了ANR
* **Broadcast ANR**,前臺的“串行廣播消息”必須在10秒內處理完畢,后臺的“串行廣播消息”必須在60秒處理完畢, 每派發串行廣播消息到一個接收器時,都會拋出一個定時消息BROADCAST\_TIMEOUT\_MSG,如果定時消息響應,則判斷是否廣播消息處理超時,超時就說明發生了ANR
* **Input ANR**,輸入事件必須在5秒內處理完畢。在派發一個輸入事件時,會判斷當前輸入事件是否需要等待,如果需要等待,則判斷是否等待已經超時,超時就說明發生了ANR
## Service處理超時源碼介紹
Service運行在應用程序的主線程,如果Service的執行時間超過20秒,則會引發ANR。
當發生Service ANR時,一般可以先排查一下在Service的生命周期函數中(onCreate(), onStartCommand()等)有沒有做耗時的操作,譬如復雜的運算、IO操作等。
如何檢測Service超時呢?Android是通過設置定時消息實現的。定時消息是由AMS的消息隊列處理的(system\_server的ActivityManager線程)。 AMS有Service運行的上下文信息,所以在AMS中設置一套超時檢測機制也是合情合理的。
Service ANR機制相對最為簡單,主體實現在[ActiveServices.java,**realStartServiceLocked** 中。 當Service的生命周期開始時,**bumpServiceExecutingLocked** 會被調用,緊接著會調**scheduleServiceTimeoutLocked**ActiveServices.java
### 埋炸彈
ActiveServices(mAm.mHandler )
~~~
private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException {
...
//發送delay消息(SERVICE_TIMEOUT_MSG),【見小節2.1.2】
bumpServiceExecutingLocked(r, execInFg, "create");
try {
...
//最終執行服務的onCreate()方法
app.thread.scheduleCreateService(r, r.serviceInfo,
mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
app.repProcState);
} catch (DeadObjectException e) {
mAm.appDiedLocked(app);
throw e;
} finally {
...
}
}
private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) {
...
scheduleServiceTimeoutLocked(r.app);
}
void scheduleServiceTimeoutLocked(ProcessRecord proc) {
if (proc.executingServices.size() == 0 || proc.thread == null) {
return;
}
long now = SystemClock.uptimeMillis();
Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_TIMEOUT_MSG);
msg.obj = proc;
//當超時后仍沒有remove該SERVICE_TIMEOUT_MSG消息,則執行service Timeout流程【見2.3.1】
mAm.mHandler.sendMessageAtTime(msg,
proc.execServicesFg ? (now+SERVICE_TIMEOUT) : (now+ SERVICE_BACKGROUND_TIMEOUT));
}
~~~
### 拆炸彈
ActivityThread.java
~~~
private void handleCreateService(CreateServiceData data) {
...
java.lang.ClassLoader cl = packageInfo.getClassLoader();
Service service = (Service) cl.loadClass(data.info.name).newInstance();
...
try {
//創建ContextImpl對象
ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
context.setOuterContext(service);
//創建Application對象
Application app = packageInfo.makeApplication(false, mInstrumentation);
service.attach(context, this, data.info.name, data.token, app,
ActivityManagerNative.getDefault());
//調用服務onCreate()方法
service.onCreate();
//拆除炸彈引線[見小節2.2.2]
ActivityManagerNative.getDefault().serviceDoneExecuting(
data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
} catch (Exception e) {
...
}
}
~~~
### 引爆炸彈
~~~
final class MainHandler extends Handler {
public void handleMessage(Message msg) {
switch (msg.what) {
case SERVICE_TIMEOUT_MSG: {
...
//【見小節2.3.2】
mServices.serviceTimeout((ProcessRecord)msg.obj);
} break;
...
}
...
}
}
~~~
serviceTimeout
~~~
void serviceTimeout(ProcessRecord proc) {
String anrMessage = null;
synchronized(mAm) {
if (proc.executingServices.size() == 0 || proc.thread == null) {
return;
}
final long now = SystemClock.uptimeMillis();
final long maxTime = now -
(proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
ServiceRecord timeout = null;
long nextTime = 0;
for (int i=proc.executingServices.size()-1; i>=0; i--) {
ServiceRecord sr = proc.executingServices.valueAt(i);
if (sr.executingStart < maxTime) {
timeout = sr;
break;
}
if (sr.executingStart > nextTime) {
nextTime = sr.executingStart;
}
}
if (timeout != null && mAm.mLruProcesses.contains(proc)) {
Slog.w(TAG, "Timeout executing service: " + timeout);
StringWriter sw = new StringWriter();
PrintWriter pw = new FastPrintWriter(sw, false, 1024);
pw.println(timeout);
timeout.dump(pw, " ");
pw.close();
mLastAnrDump = sw.toString();
mAm.mHandler.removeCallbacks(mLastAnrDumpClearer);
mAm.mHandler.postDelayed(mLastAnrDumpClearer, LAST_ANR_LIFETIME_DURATION_MSECS);
anrMessage = "executing service " + timeout.shortName;
}
}
if (anrMessage != null) {
//當存在timeout的service,則執行appNotResponding
mAm.appNotResponding(proc, null, null, false, anrMessage);
}
~~~
## Input處理超時

內核將原始事件寫入到設備節點中,InputReader在其線程循環中不斷地從EventHub中抽取原始輸入事件,進行加工處理后將加工所得的事件放入InputDispatcher的派發發隊列中。InputDispatcher則在其線程循環中將派發隊列中的事件取出,查找合適的窗口,將事件寫入到窗口的事件接收管道中。窗口事件接收線程的Looper從管道中將事件取出,交由窗口事件處理函數進行事件響應。關鍵流程有:原始輸入事件的讀取與加工;輸入事件的派發;輸入事件的發送、接收與反饋。其中輸入事件派發是指InputDispatcher不斷的從派發隊列取出事件、尋找合適的窗口進行發送的過程,輸入事件的發送是InputDispatcher通過Connection對象將事件發送給窗口的過程。
InputDispatcher與窗口之間的跨進程通信主要通過InputChannel來完成。在InputDispatcher與窗口通過InputChannel建立連接之后,就可以進行事件的發送、接收與反饋;輸入事件的發送和接收主要流程如圖所示
InputDispatcher作為中樞,不停地在遞送著輸入事件,當一個事件無法得到處理的時候,InputDispatcher不能就此死掉啊,否則系統也太容易崩潰了。?**InputDispatcher的策略是放棄掉處理不過來的事件,并發出通知(這個通知機制就是ANR)**,繼續進行下一輪消息的處理。
事件分發5s限制定義在InputDispatcher.cppInputDispatcher::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;
}
}
~~~
當應用主線程被卡住的事件,再點擊該應用其它組件也是無響應,因為事件派發是串行的,上一個事件不處理完畢,不會處理下一個事件。
Activity.onCreate執行耗時操作,不管用戶如何操作都不會發生ANR,因為輸入事件相關監聽機制還沒有建立起來;InputChannel通道還沒有建立這時是不會響應輸入事件,InputDispatcher還不能事件發送到應用窗口,ANR監聽機制也還沒有建立,所以此時是不會報告ANR的。
## ANR的報告機制
無論哪種類型的ANR發生以后,最終都會調用?AMS.appNotResponding()?方法,所謂“殊途同歸”。這個方法的職能就是向用戶或開發者報告ANR發生了。 最終的表現形式是:彈出一個對話框,告訴用戶當前某個程序無響應;輸入一大堆與ANR相關的日志,便于開發者解決問題。
最終形式我們見過很多,但輸出日志的原理是什么,未必所有人都了解,下面我們就來認識一下是如何輸出ANR日志
~~~
final void appNotResponding(ProcessRecord 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);
}
~~~
該方法的主體邏輯可以分成五個部分來看:
* 更新CPU的統計信息。這是發生ANR時,第一次CPU使用信息的采樣,采樣數據會保存在mProcessStats這個變量中
* 填充firstPids和lastPids數組。當前發生ANR的應用會首先被添加到firstPids中,這樣打印函數棧的時候,當前進程總是在trace文件的最前面
* 打印函數調用棧(StackTrace)。具體實現由dumpStackTraces()函數完成
* 更新CPU的統計信息。這是發生ANR時,第二次CPU使用信息的采樣,兩次采樣的數據分別對應ANR發生前后的CPU使用情況
* 顯示ANR對話框。拋出SHOW\_NOT\_RESPONDING\_MSG消息,AMS.MainHandler會處理這條消息,顯示AppNotRespondingDialog
當然,除了主體邏輯,發生ANR時還會輸出各種類別的日志:
* event log,通過檢索”am\_anr”關鍵字,可以找到發生ANR的應用
* main log,通過檢索”ANR in “關鍵字,可以找到ANR的信息,日志的上下文會包含CPU的使用情況
* dropbox,通過檢索”anr”類型,可以找到ANR的信息
* traces, 發生ANR時,各進程的函數調用棧信息
我們分析ANR問題,往往是從main log中的CPU使用情況和traces中的函數調用棧開始。所以,更新CPU的使用信息updateCpuStatsNow()方法和打印函數棧dumpStackTraces()方法,是系統報告ANR問題關鍵所在。
## 參考資料
[ANR機制以及問題分析](https://duanqz.github.io/2015-10-12-ANR-Analysis)
[理解Android ANR的觸發原理](http://gityuan.com/2016/07/02/android-anr/)
- Android
- 四大組件
- Activity
- Fragment
- Service
- 序列化
- Handler
- Hander介紹
- MessageQueue詳細
- 啟動流程
- 系統啟動流程
- 應用啟動流程
- Activity啟動流程
- View
- view繪制
- view事件傳遞
- choreographer
- LayoutInflater
- UI渲染概念
- Binder
- Binder原理
- Binder最大數據
- Binder小結
- Android組件
- ListView原理
- RecyclerView原理
- SharePreferences
- AsyncTask
- Sqlite
- SQLCipher加密
- 遷移與修復
- Sqlite內核
- Sqlite優化v2
- sqlite索引
- sqlite之wal
- sqlite之鎖機制
- 網絡
- 基礎
- TCP
- HTTP
- HTTP1.1
- HTTP2.0
- HTTPS
- HTTP3.0
- HTTP進化圖
- HTTP小結
- 實踐
- 網絡優化
- Json
- ProtoBuffer
- 斷點續傳
- 性能
- 卡頓
- 卡頓監控
- ANR
- ANR監控
- 內存
- 內存問題與優化
- 圖片內存優化
- 線下內存監控
- 線上內存監控
- 啟動優化
- 死鎖監控
- 崩潰監控
- 包體積優化
- UI渲染優化
- UI常規優化
- I/O監控
- 電量監控
- 第三方框架
- 網絡框架
- Volley
- Okhttp
- 網絡框架n問
- OkHttp原理N問
- 設計模式
- EventBus
- Rxjava
- 圖片
- ImageWoker
- Gilde的優化
- APT
- 依賴注入
- APT
- ARouter
- ButterKnife
- MMKV
- Jetpack
- 協程
- MVI
- Startup
- DataBinder
- 黑科技
- hook
- 運行期Java-hook技術
- 編譯期hook
- ASM
- Transform增量編譯
- 運行期Native-hook技術
- 熱修復
- 插件化
- AAB
- Shadow
- 虛擬機
- 其他
- UI自動化
- JavaParser
- Android Line
- 編譯
- 疑難雜癥
- Android11滑動異常
- 方案
- 工業化
- 模塊化
- 隱私合規
- 動態化
- 項目管理
- 業務啟動優化
- 業務架構設計
- 性能優化case
- 性能優化-排查思路
- 性能優化-現有方案
- 登錄
- 搜索
- C++
- NDK入門
- 跨平臺
- H5
- Flutter
- Flutter 性能優化
- 數據跨平臺