不管是系統開發還是應用開發,ANR 一直是揮之不去的存在!本文從 ANR 的觸發機制以及一個簡單的範例來探討 ANR 的原理和處理方法! ...
1. 核心源碼
關鍵類 | 路徑(/frameworks/base/) |
---|---|
ActiveServices.java | services/core/java/com/android/server/am/ActiveServices.java |
ActivityManagerService.java | services/core/java/com/android/server/am/ActivityManagerService.java |
AppErrors.java | services/core/java/com/android/server/am/AppErrors.java |
2. ANR 基礎認知
2.1 ANR 是什麼?
ANR(Application Not Responding),應用程式無響應,簡單一個定義,卻涵蓋了很多 Android 系統的設計思想。
首先,ANR 屬於應用程式的範疇,這不同於 SNR(System Not Respoding),SNR 反映的問題是系統進程(system_server)失去了響應能力,而 ANR 明確將問題圈定在應用程式。SNR 由 Watchdog 機制保證,ANR 由消息處理機制保證
,Android 在系統層實現了一套精密的機制來發現 ANR,核心原理是 消息調度
和超時處理
。
其次,ANR 機制 主體實現在系統層
。所有與 ANR 相關的消息,都會經過系統進程(system_server)調度,然後派發到應用進程完成對消息的實際處理,同時,系統進程設計了不同的超時限制來跟蹤消息的處理。一旦應用程式處理消息不當,超時限制就起作用了,它收集一些系統狀態,例如:CPU/IO使用情況、進程函數調用棧,並且報告用戶有進程 無響應了(ANR 對話框)
。
然後,ANR 問題 本質是一個性能問題
。ANR 機制實際上對應用程式主線程的限制,要求主線程在限定的時間內處理完一些最常見的操作(啟動服務、處理廣播、處理輸入),如果處理超時,則認為主線程已經失去了響應其他操作的能力。主線程中的 耗時操作
,例如:密集CPU運算、大量IO、複雜界面佈局等,都會降低應用程式的響應能力。
最後,部分 ANR 問題是很難分析的,有時候由於系統底層的一些影響,導致消息調度失敗,出現問題的場景又難以復現。這類 ANR 問題往往需要花費大量的時間去瞭解系統的一些行為,超出了 ANR 機制本身的範疇。
2.2 ANR 機制
分析一些初級的 ANR 問題,只需要簡單理解最終輸出的日誌即可,但對於一些由系統問題(例如:CPU 負載過高、進程卡死)引發的 ANR,就需要對整個 ANR 機制有所瞭解,才能定位出問題的原因。
ANR 機制可以分為兩部分:
✎ ANR的監測:Android 對於不同的 ANR 類型(Broadcast,Service,InputEvent)都有一套監測機制。
✎ ANR的報告:在監測到 ANR 以後,需要顯示 ANR 對話框、輸出日誌(發生 ANR 時的進程函數調用棧、CPU 使用情況等)。
2.3 ANR 的觸發原因
前面我們說過,出現 ANR 之後一個直觀現象就是系統會展示出一個 ANR 對話框。
谷歌文檔中對 ANR 產生的原因是這麼描述的:
Android 系統中的應用被 ActivityManagerService
及 WindowManagerService
兩個系統服務監控著,系統會在如下兩種情況展示出 ANR 的對話框!
✎ KeyDispatchTimeout ( 5 seconds ) :按鍵或觸摸事件在特定時間內無響應。
✎ BroadcastTimeout ( 10 seconds ):BroadcastReceiver 在特定時間內無法處理完成。
✎ ServiceTimeout ( 20 seconds ) :Service 在特定的時間內無法處理完成。
3. Service 超時監測機制
Service 運行在應用程式的主線程,如果 Service 的執行時間超過 20 秒,則會引發 ANR。
當發生 Service ANR 時,一般可以先排查一下在 Service 的生命周期函數中有沒有做 耗時的操作
,例如複雜的運算、IO 操作等。如果應用程式的代碼邏輯查不出問題,就需要深入檢查當前系統的狀態:CPU 的使用情況、系統服務的狀態等,判斷當時發生 ANR 進程是否受到 系統運行異常
的影響。
那麼,系統是如何檢測 Service 超時的呢?Android 是通過設置定時消息實現的。定時消息是由 AMS 的消息隊列處理的,AMS 有 Service 運行的上下文信息,所以在 AMS 中設置一套超時檢測機制也是合情合理的。
Service ANR 機制相對最為簡單,主體實現在ActiveServices
中。
在 Service 的啟動流程中,Service 進程 attach 到 system_server 進程後會調用 realStartServiceLocked()
方法。
3.1 realStartServiceLocked
// frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
public final class ActiveServices {
private final void realStartServiceLocked(ServiceRecord r,
ProcessRecord app, boolean execInFg) throws RemoteException {
// 發送 delay 消息(SERVICE_TIMEOUT_MSG)
bumpServiceExecutingLocked(r, execInFg, "create");
boolean created = false;
try {
// 最終執行服務的 onCreate() 方法
app.thread.scheduleCreateService(r, r.serviceInfo, mAm.
compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
app.repProcState);
... ...
}
}
}
3.2 bumpServiceExecutingLocked
private final void bumpServiceExecutingLocked(...) {
scheduleServiceTimeoutLocked(r.app);
}
3.3 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;
// 當超時後仍沒有 remove 該 SERVICE_TIMEOUT_MSG 消息,
// 通過 AMS.MainHandler 拋出一個定時消息。
mAm.mHandler.sendMessageDelayed(msg,
proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
}
上述方法通過 AMS.MainHandler
拋出一個定時消息 SERVICE_TIMEOUT_MSG
。
3.4 serviceDoneExecutingLocked
前臺進程中執行 Service,超時時間是 SERVICE_TIMEOUT(20 秒)
// How long we wait for a service to finish executing.
static final int SERVICE_TIMEOUT = 20*1000;
後臺進程中執行 Service,超時時間是 SERVICE_BACKGROUND_TIMEOUT(200 秒)
// How long we wait for a service to finish executing.
static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;
當 Service 的生命周期結束時(不會 ANR),會調用 serviceDoneExecutingLocked()
方法,之前拋出的 SERVICE_TIMEOUT_MSG
消息在這個方法中會被清除。
void serviceDoneExecutingLocked(ServiceRecord r, int type, int startId, int res) {
boolean inDestroying = mDestroyingServices.contains(r);
if (r != null) {
... ...
serviceDoneExecutingLocked(r, inDestroying, inDestroying);
}
}
private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
boolean finishing) {
... ...
if (r.executeNesting <= 0) {
if (r.app != null) {
... ...
// 當前服務所在進程中沒有正在執行的service,清除 SERVICE_TIMEOUT_MSG 消息
if (r.app.executingServices.size() == 0) {
mAm.mHandler.removeMessages(
ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
... ...
}
... ...
}
3.5 handleMessage
如果沒有 Remove 掉 SERVICE_TIMEOUT_MSG 呢?接下來我們看看對於 ANR 的處理邏輯。
在 system_server 進程中有一個 Handler 線程,名叫 ActivityManager
。
如果在超時時間內,SERVICE_TIMEOUT_MSG
沒有被清除,便會向該 Handler
線程發送一條信息 SERVICE_TIMEOUT_MSG
。
final class MainHandler extends Handler {
... ...
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
... ...
case SERVICE_TIMEOUT_MSG: {
mServices.serviceTimeout((ProcessRecord)msg.obj);
} break;
... ...
}
}
3.6 serviceTimeout
void serviceTimeout(ProcessRecord proc) {
String anrMessage = null;
synchronized(mAm) {
... ...
long nextTime = 0;
// 尋找運行超時的 Service
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;
}
}
// 判斷執行 Service 超時的進程是否在最近運行進程列表,如果不在,則忽略這個 ANR
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.mAppErrors.appNotResponding(proc, null, null, false, anrMessage);
}
}
上述方法會找到當前進程已經超時的 Service,經過一些判定後,決定要報告 ANR,最終調用 AMS.appNotResponding()
方法。
走到這一步,ANR 機制已經完成了監測報告任務,剩下的任務就是 ANR 結果的輸出,我們稱之為 ANR 的報告機制。ANR 的報告機制是通過 AMS.appNotResponding()
完成的,Broadcast 和 InputEvent 類型的 ANR 最終也都會調用這個方法。
4. ANR 信息收集過程
接下來我們看看 Android ANR 的信息收集過程!
4.1 appNotResponding
// frameworks/base/services/core/java/com/android/server/am/AppErrors.java
class AppErrors {
final void appNotResponding(ProcessRecord app, ActivityRecord activity,
ActivityRecord parent, boolean aboveSystem, final String annotation) {
... ...
long anrTime = SystemClock.uptimeMillis();
if (ActivityManagerService.MONITOR_CPU_USAGE) {
mService.updateCpuStatsNow(); // 更新 cpu 統計信息
}
boolean showBackground = Settings.Secure.
getInt(mContext.getContentResolver(),
Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
boolean isSilentANR;
synchronized (mService) {
if (mService.mShuttingDown) {
return;
} else if (app.notResponding) {
return;
} else if (app.crashing) {
return;
} else if (app.killedByAm) {
return;
} else if (app.killed) {
return;
}
// In case we come through here for the same app before completing
// this one, mark as anring now so we will bail out.
app.notResponding = true;
// 記錄 ANR 到 EventLog
EventLog.writeEvent(EventLogTags.AM_ANR, app.userId, app.pid,
app.processName, app.info.flags, annotation);
// 將當前進程添加到 firstPids
firstPids.add(app.pid);
// Don't dump other PIDs if it's a background ANR
isSilentANR = !showBackground
&& !isInterestingForBackgroundTraces(app);
if (!isSilentANR) {
int parentPid = app.pid;
if (parent != null && parent.app != null && parent.app.pid > 0) {
parentPid = parent.app.pid;
}
if (parentPid != app.pid) firstPids.add(parentPid);
// 將 system_server 進程添加到 firstPids
if (MY_PID != app.pid
&& MY_PID != parentPid) firstPids.add(MY_PID);
for (int i = mService.mLruProcesses.size() - 1; i >= 0; i--) {
ProcessRecord r = mService.mLruProcesses.get(i);
if (r != null && r.thread != null) {
int pid = r.pid;
if (pid > 0 && pid != app.pid
&& pid != parentPid && pid != MY_PID) {
if (r.persistent) {
// 將 persistent 進程添加到 firstPids
firstPids.add(pid);
} else if (r.treatLikeActivity) {
firstPids.add(pid);
} else {
// 其他進程添加到 lastPids
lastPids.put(pid, Boolean.TRUE);
}
}
}
}
}
}
// 記錄 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");
}
// 創建 CPU tracker 對象
ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);
... ...
// 輸出 traces 信息
File tracesFile = ActivityManagerService.dumpStackTraces(
true, firstPids,
(isSilentANR) ? null : processCpuTracker,
(isSilentANR) ? null : lastPids,
nativePids);
String cpuInfo = null;
if (ActivityManagerService.MONITOR_CPU_USAGE) {
mService.updateCpuStatsNow();
synchronized (mService.mProcessCpuTracker) {
cpuInfo = mService.mProcessCpuTracker.printCurrentState(anrTime);
}
// 記錄當前 CPU 負載情況
info.append(processCpuTracker.printCurrentLoad());
info.append(cpuInfo);
}
// 記錄從 anr 時間開始的 Cpu 使用情況
info.append(processCpuTracker.printCurrentState(anrTime));
// 輸出當前 ANR 的 reason,以及 CPU 使用率、負載信息
Slog.e(TAG, info.toString());
if (tracesFile == null) {
Process.sendSignal(app.pid, Process.SIGNAL_QUIT);
}
... ...
// 將 traces 文件和 CPU 使用率信息保存到 dropbox,即 data/system/dropbox 目錄
mService.addErrorToDropBox("anr", app, app.processName,
activity, parent, annotation, cpuInfo, tracesFile, null);
... ...
synchronized (mService) {
mService.mBatteryStatsService.noteProcessAnr(app.processName, app.uid);
// 後臺 ANR 的情況, 直接殺掉
if (isSilentANR) {
app.kill("bg anr", true);
return;
}
// 設置 app 的 ANR 狀態,病查詢錯誤報告 receiver
makeAppNotRespondingLocked(app,
activity != null ? activity.shortComponentName : null,
annotation != null ? "ANR " + annotation : "ANR",
info.toString());
// 彈出 ANR 對話框
Message msg = Message.obtain();
msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG;
msg.obj = new AppNotRespondingDialog.Data(app, activity, aboveSystem);
// 向 ui 線程發送,內容為 SHOW_NOT_RESPONDING_MSG 的消息
mService.mUiHandler.sendMessage(msg);
}
}
}
當發生 ANR 時, 會按順序依次執行:
✒ 1、輸出 ANR Reason
信息到 EventLog
,也就是說 ANR 觸發的時間點最接近的就是 EventLog
中輸出的 am_anr
信息;
✒ 2、收集並輸出重要進程列表中的各個線程的 traces
信息,該方法較耗時;
✒ 3、輸出當前各個進程的 CPU 使用情況
以及 CPU 負載情況
;
✒ 4、將 traces 文件
和 CPU 使用情況信息
保存到 dropbox
,即 data/system/dropbox
目錄;
✒ 5、根據進程類型,來決定直接後臺殺掉
,還是彈框告知用戶
。
ANR輸出重要進程的traces信息,這些進程包含:
✒ 1、firstPids 隊列:第一個是 ANR
進程,第二個是 system_server
,剩餘是所有 persistent
進程;
✒ 2、Native 隊列:是指 /system/bin/
目錄的 mediaserver
、sdcard
以及 surfaceflinger
進程;
✒ 3、lastPids 隊列: 是指 mLruProcesses
中的不屬於 firstPids
的所有進程。
4.2 dumpStackTraces
繼續看看 dump 出 trace 信息的流程:
// ActivityManagerService.java
public static File dumpStackTraces(boolean clearTraces, ... ,nativePids) {
... ...
if (tracesDirProp.isEmpty()) {
// 預設為 data/anr/traces.txt
String globalTracesPath =
SystemProperties.get("dalvik.vm.stack-trace-file", null);
tracesFile = new File(globalTracesPath);
try {
if (clearTraces && tracesFile.exists()) {
tracesFile.delete(); // 刪除已存在的 traces 文件
}
// 這裡會保證 data/anr/traces.txt 文件內容是全新的方式,而非追加
tracesFile.createNewFile(); // 創建 traces 文件
FileUtils.setPermissions(globalTracesPath, 0666, -1, -1);
} catch (IOException e) {
Slog.w(TAG, "Unable to prepare ANR traces file: " + tracesFile, e);
return null;
}
} else {
}
// 輸出 trace 內容
dumpStackTraces(tracesFile.getAbsolutePath(), firstPids, nativePids,
extraPids, useTombstonedForJavaTraces);
return tracesFile;
}
4.3 dumpStackTraces
// ActivityManagerService.java
private static void dumpStackTraces(String tracesFile, ...) {
final DumpStackFileObserver observer;
if (useTombstonedForJavaTraces) {
observer = null;
} else {
observer = new DumpStackFileObserver(tracesFile);
}
// We must complete all stack dumps within 20 seconds.
long remainingTime = 20 * 1000;
try {
if (observer != null) {
observer.startWatching();
}
// 首先,獲取 firstPids 進程的 stacks
if (firstPids != null) {
int num = firstPids.size();
for (int i = 0; i < num; i++) {
final long timeTaken;
if (useTombstonedForJavaTraces) {
timeTaken = dumpJavaTracesTombstoned(firstPids.get(i),
tracesFile, remainingTime);
} else {
timeTaken = observer.dumpWithTimeout(firstPids.get(i),
remainingTime);
}
... ...
}
}
// 下一步,獲取 native 進程的 stacks
if (nativePids != null) {
for (int pid : nativePids) {
... ...
// 輸出 native 進程的 trace
Debug.dumpNativeBacktraceToFileTimeout(
pid, tracesFile, (int) (nativeDumpTimeoutMs / 1000));
final long timeTaken = SystemClock.elapsedRealtime() - start;
... ...
}
}
// Lastly, dump stacks for all extra PIDs from the CPU tracker.
if (extraPids != null) {
... ...
}
}
} finally {
if (observer != null) {
observer.stopWatching();
}
}
}
4.4 小結
觸發 ANR 時系統會輸出關鍵信息:
✒ 1、將 am_anr
信息,輸出到 EventLog
;
✒ 2、獲取重要進程 trace
信息,保存到 /data/anr/traces.txt
;
✒ 3、ANR reason
以及 CPU
使用情況信息,輸出到 main log
;
✒ 4、再將 CPU使用情況
和進程 trace 文件
信息,再保存到 /data/system/dropbox
。
5. 總結
當 Service 出現 ANR 時,最終調用到 AMS.appNotResponding()
方法。
✒ 1、對於前臺服務
,則超時為 SERVICE_TIMEOUT = 20s
;
✒ 2、對於後臺服務
,則超時為 SERVICE_BACKGROUND_TIMEOUT = 200s
;
✒ 3、Service 超時檢測機制:超過一定時間沒有執行完相應操作
來觸發延時消息
,則會觸發 ANR
;