淺談Android ANR的信息收集過程
一. ANR場(chǎng)景
無(wú)論是四大組件或者進(jìn)程等只要發(fā)生ANR,最終都會(huì)調(diào)用AMS.appNotResponding()方法,下面從這個(gè)方法說起。
以下場(chǎng)景都會(huì)觸發(fā)調(diào)用AMS.appNotResponding方法:
- Service Timeout:比如前臺(tái)服務(wù)在20s內(nèi)未執(zhí)行完成;
- BroadcastQueue Timeout:比如前臺(tái)廣播在10s內(nèi)未執(zhí)行完成
- InputDispatching Timeout: 輸入事件分發(fā)超時(shí)5s,包括按鍵和觸摸事件。
二. appNotResponding處理流程
1. AMS.appNotResponding
final void appNotResponding(ProcessRecord app, ActivityRecord activity, ActivityRecord parent, boolean aboveSystem, final String annotation) { ... updateCpuStatsNow(); //第一次 更新cpu統(tǒng)計(jì)信息 synchronized (this) { //PowerManager.reboot() 會(huì)阻塞很長(zhǎng)時(shí)間,因此忽略關(guān)機(jī)時(shí)的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); // 將當(dāng)前進(jìn)程添加到firstPids firstPids.add(app.pid); int parentPid = app.pid; //將system_server進(jìn)程添加到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進(jìn)程添加到firstPids } else { lastPids.put(pid, Boolean.TRUE); //其他進(jìn)程添加到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對(duì)象 final ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true); //輸出traces信息【見小節(jié)2】 File tracesFile = dumpStackTraces(true, firstPids, processCpuTracker, lastPids, NATIVE_STACKS_OF_INTEREST); updateCpuStatsNow(); //第二次更新cpu統(tǒng)計(jì)信息 //記錄當(dāng)前各個(gè)進(jìn)程的CPU使用情況 synchronized (mProcessCpuTracker) { cpuInfo = mProcessCpuTracker.printCurrentState(anrTime); } //記錄當(dāng)前CPU負(fù)載情況 info.append(processCpuTracker.printCurrentLoad()); info.append(cpuInfo); //記錄從anr時(shí)間開始的Cpu使用情況 info.append(processCpuTracker.printCurrentState(anrTime)); //輸出當(dāng)前ANR的reason,以及CPU使用率、負(fù)載信息 Slog.e(TAG, info.toString()); //將traces文件 和 CPU使用率信息保存到dropbox,即data/system/dropbox目錄 addErrorToDropBox("anr", app, app.processName, activity, parent, annotation, cpuInfo, tracesFile, null); synchronized (this) { ... //后臺(tái)ANR的情況, 則直接殺掉 if (!showBackground && !app.isInterestingToUserLocked() && app.pid != MY_PID) { app.kill("bg anr", true); return; } //設(shè)置app的ANR狀態(tài),病查詢錯(cuò)誤報(bào)告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_進(jìn)程名_當(dāng)前日期.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對(duì)話框 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ā)送,內(nèi)容為SHOW_NOT_RESPONDING_MSG的消息 mUiHandler.sendMessage(msg); } }
當(dāng)發(fā)生ANR時(shí), 會(huì)按順序依次執(zhí)行:
- 輸出ANR Reason信息到EventLog. 也就是說ANR觸發(fā)的時(shí)間點(diǎn)最接近的就是EventLog中輸出的am_anr信息;
- 收集并輸出重要進(jìn)程列表中的各個(gè)線程的traces信息,該方法較耗時(shí); 【見小節(jié)2】
- 輸出當(dāng)前各個(gè)進(jìn)程的CPU使用情況以及CPU負(fù)載情況;
- 將traces文件和 CPU使用情況信息保存到dropbox,即data/system/dropbox目錄
- 根據(jù)進(jìn)程類型,來(lái)決定直接后臺(tái)殺掉,還是彈框告知用戶.
ANR輸出重要進(jìn)程的traces信息,這些進(jìn)程包含:
- firstPids隊(duì)列:第一個(gè)是ANR進(jìn)程,第二個(gè)是system_server,剩余是所有persistent進(jìn)程;
- Native隊(duì)列:是指/system/bin/目錄的mediaserver,sdcard 以及surfaceflinger進(jìn)程;
- lastPids隊(duì)列: 是指mLruProcesses中的不屬于firstPids的所有進(jìn)程。
2. AMS.dumpStackTraces
public static File dumpStackTraces(boolean clearTraces, ArrayList<Integer> firstPids, ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, String[] nativeProcs) { //默認(rèn)為 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 { //當(dāng)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內(nèi)容【見小節(jié)3】 dumpStackTraces(tracesPath, firstPids, processCpuTracker, lastPids, nativeProcs); return tracesFile; }
這里會(huì)保證data/anr/traces.txt文件內(nèi)容是全新的方式,而非追加。
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(); //首先,獲取最重要進(jìn)程的stacks if (firstPids != null) { try { int num = firstPids.size(); for (int i = 0; i < num; i++) { synchronized (observer) { //向目標(biāo)進(jìn)程發(fā)送signal來(lái)輸出traces Process.sendSignal(firstPids.get(i), Process.SIGNAL_QUIT); observer.wait(200); //等待直到寫關(guān)閉,或者200ms超時(shí) } } } catch (InterruptedException e) { Slog.wtf(TAG, e); } } //下一步,獲取native進(jìn)程的stacks if (nativeProcs != null) { int[] pids = Process.getPidsForCommands(nativeProcs); if (pids != null) { for (int pid : pids) { //輸出native進(jìn)程的trace【見小節(jié)4】 Debug.dumpNativeBacktraceToFile(pid, tracesPath); } } } if (processCpuTracker != null) { processCpuTracker.init(); System.gc(); processCpuTracker.update(); synchronized (processCpuTracker) { processCpuTracker.wait(500); //等待500ms } //測(cè)量CPU使用情況 processCpuTracker.update(); //從lastPids中選取CPU使用率 top 5的進(jìn)程,輸出這些進(jìn)程的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進(jìn)程的stacks;
第一個(gè)是發(fā)生ANR進(jìn)程;
第二個(gè)是system_server;
mLruProcesses中所有的persistent進(jìn)程;
2.收集Native進(jìn)程的stacks;(dumpNativeBacktraceToFile)
依次是mediaserver,sdcard,surfaceflinger進(jìn)程;
3.收集lastPids進(jìn)程的stacks;;
依次輸出CPU使用率top 5的進(jìn)程;
Tips: firstPids列表中的進(jìn)程, 兩個(gè)進(jìn)程之間會(huì)休眠200ms, 可見persistent進(jìn)程越多,則時(shí)間越長(zhǎng). top 5進(jìn)程的traces過程中, 同樣是間隔200ms, 另外進(jìn)程使用情況的收集也是比較耗時(shí).
4. dumpNativeBacktraceToFile
Debug.dumpNativeBacktraceToFile(pid, tracesPath)經(jīng)過JNI調(diào)用如下方法:
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向服務(wù)端發(fā)送dump backtrace的請(qǐng)求 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中讀取到服務(wù)端發(fā)送過來(lái)的數(shù)據(jù),并寫入buffer while ((n = TEMP_FAILURE_RETRY(read(sock_fd, buffer, sizeof(buffer)))) > 0) { //再將buffer數(shù)據(jù)輸出到traces.txt文件 if (TEMP_FAILURE_RETRY(write(fd, buffer, n)) != n) { result = -1; break; } } close(sock_fd); return result; }
可見,這個(gè)過程主要是通過向debuggerd守護(hù)進(jìn)程發(fā)送命令DEBUGGER_ACTION_DUMP_BACKTRACE, debuggerd收到該命令,在子進(jìn)程中調(diào)用 dump_backtrace()來(lái)輸出backtrace。
三. 總結(jié)
觸發(fā)ANR時(shí)系統(tǒng)會(huì)輸出關(guān)鍵信息:(這個(gè)較耗時(shí),可能會(huì)有10s)
1.將am_anr信息,輸出到EventLog.(ANR開始起點(diǎn)看EventLog)
2.獲取重要進(jìn)程trace信息,保存到/data/anr/traces.txt;(會(huì)先刪除老的文件)
Java進(jìn)程的traces;
Native進(jìn)程的traces;
3.ANR reason以及CPU使用情況信息,輸出到main log;
4.再將CPU使用情況和進(jìn)程trace文件信息,再保存到/data/system/dropbox;
整個(gè)過程中進(jìn)程Trace的輸出是最為核心的環(huán)節(jié),Java和Native進(jìn)程采用不同的策略,如下:
進(jìn)程類型 | trace命令 | 描述 |
Java | kill -3 [pid] | 不適用于Native進(jìn)程 |
Native | debuggerd -b [pid] | 也適用于Java進(jìn)程 |
說明:kill -3
命令需要虛擬機(jī)的支持,所以無(wú)法輸出Native進(jìn)程traces.而debuggerd -b [pid]
也可用于Java進(jìn)程,但信息量遠(yuǎn)沒有kill -3多。 總之,ANR信息最為重要的是dropbox信息,比如system_server_anr。
重要節(jié)點(diǎn):
- 進(jìn)程名:cat /proc/[pid]/cmdline
- 線程名:cat /proc/[tid]/comm
- Kernel棧:cat /proc/[tid]/stack
- Native棧: 解析 /proc/[pid]/maps
以上就是淺談Android ANR的信息收集過程的詳細(xì)內(nèi)容,更多關(guān)于Android ANR 的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android開發(fā)實(shí)現(xiàn)的圓角按鈕、文字陰影按鈕效果示例
這篇文章主要介紹了Android開發(fā)實(shí)現(xiàn)的圓角按鈕、文字陰影按鈕效果,涉及Android界面布局與屬性設(shè)置相關(guān)操作技巧,需要的朋友可以參考下2019-04-04android學(xué)習(xí)筆記之View的滑動(dòng)
Android開發(fā)中我們常常需要View滑動(dòng)實(shí)現(xiàn)一些絢麗的效果來(lái)優(yōu)化用戶體驗(yàn),下面這篇文章主要給大家介紹了關(guān)于android學(xué)習(xí)筆記之View滑動(dòng)的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-01-01android shape實(shí)現(xiàn)陰影或模糊邊效果
這篇文章主要介紹了android shape實(shí)現(xiàn)陰影或模糊邊效果,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-10-10解析Android應(yīng)用程序運(yùn)行機(jī)制
這篇文章主要介紹了Android應(yīng)用程序運(yùn)行機(jī)制,有需要的朋友可以參考一下2014-01-01Android獲取手機(jī)本機(jī)號(hào)碼的實(shí)現(xiàn)方法
這篇文章主要介紹了Android獲取手機(jī)本機(jī)號(hào)碼的實(shí)現(xiàn)方法的相關(guān)資料,希望通過本文大家能夠?qū)崿F(xiàn)這樣的方法,需要的朋友可以參考下2017-10-10AndroidStudio安全管理簽名文件keystroe和簽名密碼(星空武哥)
我們?cè)谑褂肁ndroidStudio進(jìn)行release版的apk簽名的時(shí)候,往往都是將簽名文件keystore放在項(xiàng)目中,密碼寫在build.gradle中,keystore和密碼就隨著代碼上傳到了Git倉(cāng)庫(kù)中了,這樣往往很不安全,因?yàn)檫@樣被人獲取2017-09-09Kotlin協(xié)程低級(jí)api startCoroutine與ContinuationInterceptor
這篇文章主要為大家介紹了Kotlin協(xié)程低級(jí)api startCoroutine與ContinuationInterceptor示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01