淺談Android ANR的信息收集過程
一. ANR場景
無論是四大組件或者進程等只要發(fā)生ANR,最終都會調用AMS.appNotResponding()方法,下面從這個方法說起。
以下場景都會觸發(fā)調用AMS.appNotResponding方法:
- Service Timeout:比如前臺服務在20s內未執(zhí)行完成;
- BroadcastQueue Timeout:比如前臺廣播在10s內未執(zhí)行完成
- InputDispatching Timeout: 輸入事件分發(fā)超時5s,包括按鍵和觸摸事件。
二. appNotResponding處理流程
1. AMS.appNotResponding
final void appNotResponding(ProcessRecord app, ActivityRecord activity, ActivityRecord parent, boolean aboveSystem, final String annotation) {
...
updateCpuStatsNow(); //第一次 更新cpu統(tǒng)計信息
synchronized (this) {
//PowerManager.reboot() 會阻塞很長時間,因此忽略關機時的ANR
if (mShuttingDown) {
return;
} else if (app.notResponding) {
return;
} else if (app.crashing) {
return;
}
//記錄ANR到EventLog
EventLog.writeEvent(EventLogTags.AM_ANR, app.userId, app.pid,
app.processName, app.info.flags, annotation);
// 將當前進程添加到firstPids
firstPids.add(app.pid);
int parentPid = app.pid;
//將system_server進程添加到firstPids
if (MY_PID != app.pid && MY_PID != parentPid) firstPids.add(MY_PID);
for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
ProcessRecord r = 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) {
firstPids.add(pid); //將persistent進程添加到firstPids
} else {
lastPids.put(pid, Boolean.TRUE); //其他進程添加到lastPids
}
}
}
}
}
// 記錄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");
}
//創(chuàng)建CPU tracker對象
final ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);
//輸出traces信息【見小節(jié)2】
File tracesFile = dumpStackTraces(true, firstPids, processCpuTracker,
lastPids, NATIVE_STACKS_OF_INTEREST);
updateCpuStatsNow(); //第二次更新cpu統(tǒng)計信息
//記錄當前各個進程的CPU使用情況
synchronized (mProcessCpuTracker) {
cpuInfo = 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());
//將traces文件 和 CPU使用率信息保存到dropbox,即data/system/dropbox目錄
addErrorToDropBox("anr", app, app.processName, activity, parent, annotation,
cpuInfo, tracesFile, null);
synchronized (this) {
...
//后臺ANR的情況, 則直接殺掉
if (!showBackground && !app.isInterestingToUserLocked() && app.pid != MY_PID) {
app.kill("bg anr", true);
return;
}
//設置app的ANR狀態(tài),病查詢錯誤報告receiver
makeAppNotRespondingLocked(app,
activity != null ? activity.shortComponentName : null,
annotation != null ? "ANR " + annotation : "ANR",
info.toString());
//重命名trace文件
String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null);
if (tracesPath != null && tracesPath.length() != 0) {
//traceRenameFile = "/data/anr/traces.txt"
File traceRenameFile = new File(tracesPath);
String newTracesPath;
int lpos = tracesPath.lastIndexOf (".");
if (-1 != lpos)
// 新的traces文件= /data/anr/traces_進程名_當前日期.txt
newTracesPath = tracesPath.substring (0, lpos) + "_" + app.processName + "_" + mTraceDateFormat.format(new Date()) + tracesPath.substring (lpos);
else
newTracesPath = tracesPath + "_" + app.processName;
traceRenameFile.renameTo(new File(newTracesPath));
}
//彈出ANR對話框
Message msg = Message.obtain();
HashMap<String, Object> map = new HashMap<String, Object>();
msg.what = SHOW_NOT_RESPONDING_MSG;
msg.obj = map;
msg.arg1 = aboveSystem ? 1 : 0;
map.put("app", app);
if (activity != null) {
map.put("activity", activity);
}
//向ui線程發(fā)送,內容為SHOW_NOT_RESPONDING_MSG的消息
mUiHandler.sendMessage(msg);
}
}
當發(fā)生ANR時, 會按順序依次執(zhí)行:
- 輸出ANR Reason信息到EventLog. 也就是說ANR觸發(fā)的時間點最接近的就是EventLog中輸出的am_anr信息;
- 收集并輸出重要進程列表中的各個線程的traces信息,該方法較耗時; 【見小節(jié)2】
- 輸出當前各個進程的CPU使用情況以及CPU負載情況;
- 將traces文件和 CPU使用情況信息保存到dropbox,即data/system/dropbox目錄
- 根據進程類型,來決定直接后臺殺掉,還是彈框告知用戶.
ANR輸出重要進程的traces信息,這些進程包含:
- firstPids隊列:第一個是ANR進程,第二個是system_server,剩余是所有persistent進程;
- Native隊列:是指/system/bin/目錄的mediaserver,sdcard 以及surfaceflinger進程;
- lastPids隊列: 是指mLruProcesses中的不屬于firstPids的所有進程。
2. AMS.dumpStackTraces
public static File dumpStackTraces(boolean clearTraces, ArrayList<Integer> firstPids, ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, String[] nativeProcs) {
//默認為 data/anr/traces.txt
String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null);
if (tracesPath == null || tracesPath.length() == 0) {
return null;
}
File tracesFile = new File(tracesPath);
try {
//當clearTraces,則刪除已存在的traces文件
if (clearTraces && tracesFile.exists()) tracesFile.delete();
//創(chuàng)建traces文件
tracesFile.createNewFile();
FileUtils.setPermissions(tracesFile.getPath(), 0666, -1, -1);
} catch (IOException e) {
return null;
}
//輸出trace內容【見小節(jié)3】
dumpStackTraces(tracesPath, firstPids, processCpuTracker, lastPids, nativeProcs);
return tracesFile;
}
這里會保證data/anr/traces.txt文件內容是全新的方式,而非追加。
3. AMS.dumpStackTraces
private static void dumpStackTraces(String tracesPath, ArrayList<Integer> firstPids, ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, String[] nativeProcs) {
FileObserver observer = new FileObserver(tracesPath, FileObserver.CLOSE_WRITE) {
@Override
public synchronized void onEvent(int event, String path) { notify(); }
};
try {
observer.startWatching();
//首先,獲取最重要進程的stacks
if (firstPids != null) {
try {
int num = firstPids.size();
for (int i = 0; i < num; i++) {
synchronized (observer) {
//向目標進程發(fā)送signal來輸出traces
Process.sendSignal(firstPids.get(i), Process.SIGNAL_QUIT);
observer.wait(200); //等待直到寫關閉,或者200ms超時
}
}
} catch (InterruptedException e) {
Slog.wtf(TAG, e);
}
}
//下一步,獲取native進程的stacks
if (nativeProcs != null) {
int[] pids = Process.getPidsForCommands(nativeProcs);
if (pids != null) {
for (int pid : pids) {
//輸出native進程的trace【見小節(jié)4】
Debug.dumpNativeBacktraceToFile(pid, tracesPath);
}
}
}
if (processCpuTracker != null) {
processCpuTracker.init();
System.gc();
processCpuTracker.update();
synchronized (processCpuTracker) {
processCpuTracker.wait(500); //等待500ms
}
//測量CPU使用情況
processCpuTracker.update();
//從lastPids中選取CPU使用率 top 5的進程,輸出這些進程的stacks
final int N = processCpuTracker.countWorkingStats();
int numProcs = 0;
for (int i=0; i<N && numProcs<5; i++) {
ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
if (lastPids.indexOfKey(stats.pid) >= 0) {
numProcs++;
synchronized (observer) {
Process.sendSignal(stats.pid, Process.SIGNAL_QUIT);
observer.wait(200);
}
}
}
}
} finally {
observer.stopWatching();
}
}
該方法的主要功能,依次輸出:
1.收集firstPids進程的stacks;
第一個是發(fā)生ANR進程;
第二個是system_server;
mLruProcesses中所有的persistent進程;
2.收集Native進程的stacks;(dumpNativeBacktraceToFile)
依次是mediaserver,sdcard,surfaceflinger進程;
3.收集lastPids進程的stacks;;
依次輸出CPU使用率top 5的進程;
Tips: firstPids列表中的進程, 兩個進程之間會休眠200ms, 可見persistent進程越多,則時間越長. top 5進程的traces過程中, 同樣是間隔200ms, 另外進程使用情況的收集也是比較耗時.
4. dumpNativeBacktraceToFile
Debug.dumpNativeBacktraceToFile(pid, tracesPath)經過JNI調用如下方法:
static void android_os_Debug_dumpNativeBacktraceToFile(JNIEnv* env, jobject clazz, jint pid, jstring fileName) {
...
const jchar* str = env->GetStringCritical(fileName, 0);
String8 fileName8;
if (str) {
fileName8 = String8(reinterpret_cast<const char16_t*>(str),
env->GetStringLength(fileName));
env->ReleaseStringCritical(fileName, str);
}
//打開/data/anr/traces.txt
int fd = open(fileName8.string(), O_CREAT | O_WRONLY | O_NOFOLLOW, 0666); /* -rw-rw-rw- */
...
if (lseek(fd, 0, SEEK_END) < 0) {
fprintf(stderr, "lseek: %s\n", strerror(errno));
} else {
//【見小節(jié)5】
dump_backtrace_to_file(pid, fd);
}
close(fd);
}
5. dump_backtrace_to_file
[-> debugger.c]
int dump_backtrace_to_file(pid_t tid, int fd) {
return dump_backtrace_to_file_timeout(tid, fd, 0);
}
int dump_backtrace_to_file_timeout(pid_t tid, int fd, int timeout_secs) {
//通過socket向服務端發(fā)送dump backtrace的請求
int sock_fd = make_dump_request(DEBUGGER_ACTION_DUMP_BACKTRACE, tid, timeout_secs);
if (sock_fd < 0) {
return -1;
}
int result = 0;
char buffer[1024];
ssize_t n;
//阻塞等待,從sock_fd中讀取到服務端發(fā)送過來的數據,并寫入buffer
while ((n = TEMP_FAILURE_RETRY(read(sock_fd, buffer, sizeof(buffer)))) > 0) {
//再將buffer數據輸出到traces.txt文件
if (TEMP_FAILURE_RETRY(write(fd, buffer, n)) != n) {
result = -1;
break;
}
}
close(sock_fd);
return result;
}
可見,這個過程主要是通過向debuggerd守護進程發(fā)送命令DEBUGGER_ACTION_DUMP_BACKTRACE, debuggerd收到該命令,在子進程中調用 dump_backtrace()來輸出backtrace。
三. 總結
觸發(fā)ANR時系統(tǒng)會輸出關鍵信息:(這個較耗時,可能會有10s)
1.將am_anr信息,輸出到EventLog.(ANR開始起點看EventLog)
2.獲取重要進程trace信息,保存到/data/anr/traces.txt;(會先刪除老的文件)
Java進程的traces;
Native進程的traces;
3.ANR reason以及CPU使用情況信息,輸出到main log;
4.再將CPU使用情況和進程trace文件信息,再保存到/data/system/dropbox;
整個過程中進程Trace的輸出是最為核心的環(huán)節(jié),Java和Native進程采用不同的策略,如下:
| 進程類型 | trace命令 | 描述 |
| Java | kill -3 [pid] | 不適用于Native進程 |
| Native | debuggerd -b [pid] | 也適用于Java進程 |
說明:kill -3命令需要虛擬機的支持,所以無法輸出Native進程traces.而debuggerd -b [pid]也可用于Java進程,但信息量遠沒有kill -3多。 總之,ANR信息最為重要的是dropbox信息,比如system_server_anr。
重要節(jié)點:
- 進程名:cat /proc/[pid]/cmdline
- 線程名:cat /proc/[tid]/comm
- Kernel棧:cat /proc/[tid]/stack
- Native棧: 解析 /proc/[pid]/maps
以上就是淺談Android ANR的信息收集過程的詳細內容,更多關于Android ANR 的資料請關注腳本之家其它相關文章!
相關文章
Android開發(fā)實現的圓角按鈕、文字陰影按鈕效果示例
這篇文章主要介紹了Android開發(fā)實現的圓角按鈕、文字陰影按鈕效果,涉及Android界面布局與屬性設置相關操作技巧,需要的朋友可以參考下2019-04-04
AndroidStudio安全管理簽名文件keystroe和簽名密碼(星空武哥)
我們在使用AndroidStudio進行release版的apk簽名的時候,往往都是將簽名文件keystore放在項目中,密碼寫在build.gradle中,keystore和密碼就隨著代碼上傳到了Git倉庫中了,這樣往往很不安全,因為這樣被人獲取2017-09-09
Kotlin協程低級api startCoroutine與ContinuationInterceptor
這篇文章主要為大家介紹了Kotlin協程低級api startCoroutine與ContinuationInterceptor示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-01-01

