目錄總結(jié)
- 01.拋出異常導(dǎo)致崩潰分析
- 02.RuntimeInit類分析
- 03.Looper停止App就退出嗎
- 04.handleApplicationCrash
- 05.native_crash如何監(jiān)控
- 06.ANR是如何監(jiān)控的
- 07.回過頭看addErrorToDropBox
前沿
- 上一篇整體介紹了crash崩潰庫崩潰重啟垒在,崩潰記錄記錄试溯,查看以及分享日志等功能菠赚。
- 項目地址:https://github.com/yangchong211/YCAndroidTool
- 歡迎star妒御,哈哈哈
01.拋出異常導(dǎo)致崩潰分析
- 線程中拋出異常以后的處理邏輯。
- 一旦線程出現(xiàn)拋出異常,并且我們沒有捕捉的情況下,JVM將調(diào)用Thread中的dispatchUncaughtException方法把異常傳遞給線程的未捕獲異常處理器幻妓。
- 如果沒有設(shè)置uncaughtExceptionHandler,將使用線程所在的線程組來處理這個未捕獲異常劫拢。線程組ThreadGroup實現(xiàn)了UncaughtExceptionHandler,所以可以用來處理未捕獲異常。
public final void dispatchUncaughtException(Throwable e) { Thread.UncaughtExceptionHandler initialUeh = Thread.getUncaughtExceptionPreHandler(); if (initialUeh != null) { try { initialUeh.uncaughtException(this, e); } catch (RuntimeException | Error ignored) { // Throwables thrown by the initial handler are ignored } } getUncaughtExceptionHandler().uncaughtException(this, e); } public static UncaughtExceptionHandler getUncaughtExceptionPreHandler() { return uncaughtExceptionPreHandler; } public UncaughtExceptionHandler getUncaughtExceptionHandler() { return uncaughtExceptionHandler != null ? uncaughtExceptionHandler : group; } private ThreadGroup group;
- 然后看一下ThreadGroup中實現(xiàn)uncaughtException(Thread t, Throwable e)方法侨歉,代碼如下
- 默認(rèn)情況下,線程組處理未捕獲異常的邏輯是偶洋,首先將異常消息通知給父線程組,
- 然后嘗試?yán)靡粋€默認(rèn)的defaultUncaughtExceptionHandler來處理異常初烘,
- 如果沒有默認(rèn)的異常處理器則將錯誤信息輸出到System.err涡真。
- 也就是JVM提供給我們設(shè)置每個線程的具體的未捕獲異常處理器,也提供了設(shè)置默認(rèn)異常處理器的方法肾筐。
public void uncaughtException(Thread t, Throwable e) { if (parent != null) { parent.uncaughtException(t, e); } else { Thread.UncaughtExceptionHandler ueh = Thread.getDefaultUncaughtExceptionHandler(); if (ueh != null) { ueh.uncaughtException(t, e); } else if (!(e instanceof ThreadDeath)) { System.err.print("Exception in thread \"" + t.getName() + "\" "); e.printStackTrace(System.err); } } }
- 既然Android遇到異常會發(fā)生崩潰哆料,然后找一些哪里用到設(shè)置setDefaultUncaughtExceptionHandler,即可定位到RuntimeInit類吗铐。
02.RuntimeInit類分析
- 然后看一下RuntimeInit類东亦,由于是java代碼,所以首先找main方法入口唬渗。代碼如下所示
public static final void main(String[] argv) { enableDdms(); if (argv.length == 2 && argv[1].equals("application")) { if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application"); redirectLogStreams(); } else { if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting tool"); } commonInit(); /* * Now that we're running in interpreted code, call back into native code * to run the system. */ nativeFinishInit(); if (DEBUG) Slog.d(TAG, "Leaving RuntimeInit!"); }
- 然后再來看一下commonInit()方法典阵,看看里面做了什么操作?
- 可以發(fā)現(xiàn)這里調(diào)用了setDefaultUncaughtExceptionHandler方法镊逝,設(shè)置了自定義的Handler類
protected static final void commonInit() { if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!"); /* * set handlers; these apply to all threads in the VM. Apps can replace * the default handler, but not the pre handler. */ LoggingHandler loggingHandler = new LoggingHandler(); Thread.setUncaughtExceptionPreHandler(loggingHandler); Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler)); initialized = true; }
-
接著看一下KillApplicationHandler類壮啊,可以發(fā)現(xiàn)該類實現(xiàn)了Thread.UncaughtExceptionHandler 接口
private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler { private final LoggingHandler mLoggingHandler; @Override public void uncaughtException(Thread t, Throwable e) { try { ensureLogging(t, e); // Don't re-enter -- avoid infinite loops if crash-reporting crashes. if (mCrashing) return; mCrashing = true; // Try to end profiling. If a profiler is running at this point, and we kill the // process (below), the in-memory buffer will be lost. So try to stop, which will // flush the buffer. (This makes method trace profiling useful to debug crashes.) if (ActivityThread.currentActivityThread() != null) { ActivityThread.currentActivityThread().stopProfiling(); } // Bring up crash dialog, wait for it to be dismissed ActivityManager.getService().handleApplicationCrash( mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e)); } catch (Throwable t2) { if (t2 instanceof DeadObjectException) { // System process is dead; ignore } else { try { Clog_e(TAG, "Error reporting crash", t2); } catch (Throwable t3) { // Even Clog_e() fails! Oh well. } } } finally { // Try everything to make sure this process goes away. Process.killProcess(Process.myPid()); System.exit(10); } } }
- 得出結(jié)論
- 其實在fork出app進(jìn)程的時候,系統(tǒng)已經(jīng)為app設(shè)置了一個異常處理撑蒜,并且最終崩潰后會直接導(dǎo)致執(zhí)行該handler的finallly方法最后殺死app直接退出app歹啼。如果你要自己處理,你可以自己實現(xiàn)Thread.UncaughtExceptionHandler座菠。
03.Looper停止App就退出嗎
- looper如果停止了狸眼,那么app會退出嗎,先做個實驗看一下浴滴。代碼如下所示
- 可以發(fā)現(xiàn)調(diào)用這句話拓萌,是會讓app退出的。會報錯崩潰日志是:java.lang.IllegalStateException: Main thread not allowed to quit.
Looper.getMainLooper().quit(); //下面這種是安全退出 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { Looper.getMainLooper().quitSafely(); }
- 然后看一下Looper中quit方法源碼
- Looper的quit方法源碼如下:
public void quit() { mQueue.quit(false); }
- Looper的quitSafely方法源碼如下:
public void quitSafely() { mQueue.quit(true); }
- 以上兩個方法中mQueue是MessageQueue類型的對象升略,二者都調(diào)用了MessageQueue中的quit方法微王,MessageQueue的quit方法源碼如下:
- 可以發(fā)現(xiàn)上面調(diào)用了quit方法,即會出現(xiàn)出現(xiàn)崩潰品嚣,主要原因是因為調(diào)用prepare()-->new Looper(true)--->new MessageQueue(true)--->mQuitAllowed設(shè)置為true
void quit(boolean safe) { if (!mQuitAllowed) { throw new IllegalStateException("Main thread not allowed to quit."); } synchronized (this) { if (mQuitting) { return; } mQuitting = true; if (safe) { removeAllFutureMessagesLocked(); } else { removeAllMessagesLocked(); } // We can assume mPtr != 0 because mQuitting was previously false. nativeWake(mPtr); } }
- 通過觀察以上源碼我們可以發(fā)現(xiàn):
- 當(dāng)我們調(diào)用Looper的quit方法時骂远,實際上執(zhí)行了MessageQueue中的removeAllMessagesLocked方法,該方法的作用是把MessageQueue消息池中所有的消息全部清空腰根,無論是延遲消息(延遲消息是指通過sendMessageDelayed或通過postDelayed等方法發(fā)送的需要延遲執(zhí)行的消息)還是非延遲消息。
- 當(dāng)我們調(diào)用Looper的quitSafely方法時拓型,實際上執(zhí)行了MessageQueue中的removeAllFutureMessagesLocked方法额嘿,通過名字就可以看出瘸恼,該方法只會清空MessageQueue消息池中所有的延遲消息,并將消息池中所有的非延遲消息派發(fā)出去讓Handler去處理册养,quitSafely相比于quit方法安全之處在于清空消息之前會派發(fā)所有的非延遲消息东帅。
- 無論是調(diào)用了quit方法還是quitSafely方法只會,Looper就不再接收新的消息球拦。即在調(diào)用了Looper的quit或quitSafely方法之后靠闭,消息循環(huán)就終結(jié)了,這時候再通過Handler調(diào)用sendMessage或post等方法發(fā)送消息時均返回false坎炼,表示消息沒有成功放入消息隊列MessageQueue中愧膀,因為消息隊列已經(jīng)退出了。
- 需要注意的是Looper的quit方法從API Level 1就存在了谣光,但是Looper的quitSafely方法從API Level 18才添加進(jìn)來檩淋。
04.handleApplicationCrash
- 在KillApplicationHandler類中的uncaughtException方法,可以看到ActivityManager.getService().handleApplicationCrash被調(diào)用萄金,那么這個是用來做什么的呢蟀悦?
- ActivityManager.getService().handleApplicationCrash-->ActivityManagerService.handleApplicationCrash-->handleApplicationCrashInner方法
- 從下面可以看出,若傳入app為null時,processName就設(shè)置為system_server
public void handleApplicationCrash(IBinder app, ApplicationErrorReport.ParcelableCrashInfo crashInfo) { ProcessRecord r = findAppProcess(app, "Crash"); final String processName = app == null ? "system_server" : (r == null ? "unknown" : r.processName); handleApplicationCrashInner("crash", r, processName, crashInfo); }
- 然后接著看一下handleApplicationCrashInner方法做了什么
- 調(diào)用addErrorToDropBox將應(yīng)用crash,進(jìn)行封裝輸出。
void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName, ApplicationErrorReport.CrashInfo crashInfo) { addErrorToDropBox(eventType, r, processName, null, null, null, null, null, crashInfo); mAppErrors.crashApplication(r, crashInfo); }
05.native_crash如何監(jiān)控
- native_crash氧敢,顧名思義日戈,就是native層發(fā)生的crash。其實他是通過一個NativeCrashListener線程去監(jiān)控的孙乖。
final class NativeCrashListener extends Thread { ... @Override public void run() { final byte[] ackSignal = new byte[1]; ... // The file system entity for this socket is created with 0777 perms, owned // by system:system. selinux restricts things so that only crash_dump can // access it. { File socketFile = new File(DEBUGGERD_SOCKET_PATH); if (socketFile.exists()) { socketFile.delete(); } } try { FileDescriptor serverFd = Os.socket(AF_UNIX, SOCK_STREAM, 0); final UnixSocketAddress sockAddr = UnixSocketAddress.createFileSystem( DEBUGGERD_SOCKET_PATH); Os.bind(serverFd, sockAddr); Os.listen(serverFd, 1); Os.chmod(DEBUGGERD_SOCKET_PATH, 0777); //1.一直循環(huán)地讀peerFd文件,若發(fā)生存在,則進(jìn)入consumeNativeCrashData while (true) { FileDescriptor peerFd = null; try { if (MORE_DEBUG) Slog.v(TAG, "Waiting for debuggerd connection"); peerFd = Os.accept(serverFd, null /* peerAddress */); if (MORE_DEBUG) Slog.v(TAG, "Got debuggerd socket " + peerFd); if (peerFd != null) { // the reporting thread may take responsibility for // acking the debugger; make sure we play along. //2.進(jìn)入native crash數(shù)據(jù)處理流程 consumeNativeCrashData(peerFd); } } catch (Exception e) { Slog.w(TAG, "Error handling connection", e); } finally { ... } } } catch (Exception e) { Slog.e(TAG, "Unable to init native debug socket!", e); } } // Read a crash report from the connection void consumeNativeCrashData(FileDescriptor fd) { try { ... //3.啟動NativeCrashReporter作為上報錯誤的新線程 final String reportString = new String(os.toByteArray(), "UTF-8"); (new NativeCrashReporter(pr, signal, reportString)).start(); } catch (Exception e) { ... } } }
- 上報native_crash的線程-->NativeCrashReporter:
class NativeCrashReporter extends Thread { ProcessRecord mApp; int mSignal; String mCrashReport; NativeCrashReporter(ProcessRecord app, int signal, String report) { super("NativeCrashReport"); mApp = app; mSignal = signal; mCrashReport = report; } @Override public void run() { try { //1.包裝崩潰信息 CrashInfo ci = new CrashInfo(); ci.exceptionClassName = "Native crash"; ci.exceptionMessage = Os.strsignal(mSignal); ci.throwFileName = "unknown"; ci.throwClassName = "unknown"; ci.throwMethodName = "unknown"; ci.stackTrace = mCrashReport; if (DEBUG) Slog.v(TAG, "Calling handleApplicationCrash()"); //2.轉(zhuǎn)到ams中處理,跟普通crash一致,只是類型不一樣 mAm.handleApplicationCrashInner("native_crash", mApp, mApp.processName, ci); if (DEBUG) Slog.v(TAG, "<-- handleApplicationCrash() returned"); } catch (Exception e) { Slog.e(TAG, "Unable to report native crash", e); } } }
- native crash跟到這里就結(jié)束了浙炼,后面的流程就是跟application crash一樣,都會走到addErrorToDropBox中的圆,這個最后在說鼓拧。
06.ANR是如何監(jiān)控的
- 這里就不討論每種anr發(fā)生后的原因和具體的流程了,直接跳到已經(jīng)觸發(fā)ANR的位置越妈。AppErrors.appNotResponding:
final void appNotResponding(ProcessRecord app, ActivityRecord activity, ActivityRecord parent, boolean aboveSystem, final String annotation) { ArrayList<Integer> firstPids = new ArrayList<Integer>(5); SparseArray<Boolean> lastPids = new SparseArray<Boolean>(20); if (mService.mController != null) { try { //1.判斷是否繼續(xù)后面的流程,還是直接kill掉當(dāng)前進(jìn)程 // 0 == continue, -1 = kill process immediately int res = mService.mController.appEarlyNotResponding( app.processName, app.pid, annotation); if (res < 0 && app.pid != MY_PID) { app.kill("anr", true); } } catch (RemoteException e) { mService.mController = null; Watchdog.getInstance().setActivityController(null); } } //2.記錄發(fā)生anr的時間 long anrTime = SystemClock.uptimeMillis(); //3.更新cpu使用情況 if (ActivityManagerService.MONITOR_CPU_USAGE) { mService.updateCpuStatsNow(); } //可以在設(shè)置中設(shè)置發(fā)生anr后,是彈框顯示還是后臺處理,默認(rèn)是后臺 // Unless configured otherwise, swallow ANRs in background processes & kill the process. boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0; boolean isSilentANR; synchronized (mService) { ... // 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; //3.將anr寫入event log中 EventLog.writeEvent(EventLogTags.AM_ANR, app.userId, app.pid, app.processName, app.info.flags, annotation); // Dump thread traces as quickly as we can, starting with "interesting" processes. 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); 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) { firstPids.add(pid); if (DEBUG_ANR) Slog.i(TAG, "Adding persistent proc: " + r); } else if (r.treatLikeActivity) { firstPids.add(pid); if (DEBUG_ANR) Slog.i(TAG, "Adding likely IME: " + r); } else { lastPids.put(pid, Boolean.TRUE); if (DEBUG_ANR) Slog.i(TAG, "Adding ANR proc: " + r); } } } } } } // 4.將主要的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"); } ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true); ArrayList<Integer> nativePids = null; // don't dump native PIDs for background ANRs unless it is the process of interest String[] nativeProc = null; if (isSilentANR) { for (int i = 0; i < NATIVE_STACKS_OF_INTEREST.length; i++) { if (NATIVE_STACKS_OF_INTEREST[i].equals(app.processName)) { nativeProc = new String[] { app.processName }; break; } } int[] pid = nativeProc == null ? null : Process.getPidsForCommands(nativeProc); if(pid != null){ nativePids = new ArrayList<Integer>(pid.length); for (int i : pid) { nativePids.add(i); } } } else { nativePids = Watchdog.getInstance().getInterestingNativePids(); } //5.dump出stacktraces文件 // For background ANRs, don't pass the ProcessCpuTracker to // avoid spending 1/2 second collecting stats to rank lastPids. File tracesFile = ActivityManagerService.dumpStackTraces( true, firstPids, (isSilentANR) ? null : processCpuTracker, (isSilentANR) ? null : lastPids, nativePids); String cpuInfo = null; if (ActivityManagerService.MONITOR_CPU_USAGE) { //6.再次更新cpu使用情況 mService.updateCpuStatsNow(); synchronized (mService.mProcessCpuTracker) { //7.打印anr時cpu使用狀態(tài) cpuInfo = mService.mProcessCpuTracker.printCurrentState(anrTime); } info.append(processCpuTracker.printCurrentLoad()); info.append(cpuInfo); } info.append(processCpuTracker.printCurrentState(anrTime)); //8.當(dāng)traces文件不存在時,只能打印線程日志了 if (tracesFile == null) { // There is no trace file, so dump (only) the alleged culprit's threads to the log Process.sendSignal(app.pid, Process.SIGNAL_QUIT); } ... //9.關(guān)鍵,回到了我們熟悉的addErrorToDropBox,進(jìn)行錯誤信息包裝跟上傳了 mService.addErrorToDropBox("anr", app, app.processName, activity, parent, annotation, cpuInfo, tracesFile, null); if (mService.mController != null) { try { //10.根據(jù)appNotResponding返回結(jié)果,看是否繼續(xù)等待,還是結(jié)束當(dāng)前進(jìn)程 // 0 == show dialog, 1 = keep waiting, -1 = kill process immediately int res = mService.mController.appNotResponding( app.processName, app.pid, info.toString()); if (res != 0) { if (res < 0 && app.pid != MY_PID) { app.kill("anr", true); } else { synchronized (mService) { mService.mServices.scheduleServiceTimeoutLocked(app); } } return; } } catch (RemoteException e) { mService.mController = null; Watchdog.getInstance().setActivityController(null); } } ... }
- 我們來看一下traces文件是怎么dump出來的:
public static File dumpStackTraces(boolean clearTraces, ArrayList<Integer> firstPids, ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, ArrayList<Integer> nativePids) { ArrayList<Integer> extraPids = null; //1.測量CPU的使用情況季俩,以便在請求時對頂級用戶進(jìn)行實際的采樣。 if (processCpuTracker != null) { processCpuTracker.init(); try { Thread.sleep(200); } catch (InterruptedException ignored) { } processCpuTracker.update(); // 2.爬取頂級應(yīng)用到的cpu使用情況 final int N = processCpuTracker.countWorkingStats(); extraPids = new ArrayList<>(); for (int i = 0; i < N && extraPids.size() < 5; i++) { ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i); if (lastPids.indexOfKey(stats.pid) >= 0) { if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for extra pid " + stats.pid); extraPids.add(stats.pid); } else if (DEBUG_ANR) { Slog.d(TAG, "Skipping next CPU consuming process, not a java proc: " + stats.pid); } } } //3.讀取trace文件的保存目錄 File tracesFile; final String tracesDirProp = SystemProperties.get("dalvik.vm.stack-trace-dir", ""); if (tracesDirProp.isEmpty()) { ... String globalTracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null); ... } else { ... } //4.傳入指定目錄,進(jìn)入實際dump邏輯 dumpStackTraces(tracesFile.getAbsolutePath(), firstPids, nativePids, extraPids, useTombstonedForJavaTraces); return tracesFile; }
- dumpStackTraces
private static void dumpStackTraces(String tracesFile, ArrayList<Integer> firstPids, ArrayList<Integer> nativePids, ArrayList<Integer> extraPids, boolean useTombstonedForJavaTraces) { ... final DumpStackFileObserver observer; if (useTombstonedForJavaTraces) { observer = null; } else { // Use a FileObserver to detect when traces finish writing. // The order of traces is considered important to maintain for legibility. observer = new DumpStackFileObserver(tracesFile); } //我們必須在20秒內(nèi)完成所有堆棧轉(zhuǎn)儲梅掠。 long remainingTime = 20 * 1000; try { if (observer != null) { observer.startWatching(); } // 首先收集所有最重要的pid堆棧酌住。 if (firstPids != null) { int num = firstPids.size(); for (int i = 0; i < num; i++) { if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for pid " + firstPids.get(i)); final long timeTaken; if (useTombstonedForJavaTraces) { timeTaken = dumpJavaTracesTombstoned(firstPids.get(i), tracesFile, remainingTime); } else { timeTaken = observer.dumpWithTimeout(firstPids.get(i), remainingTime); } remainingTime -= timeTaken; if (remainingTime <= 0) { Slog.e(TAG, "Aborting stack trace dump (current firstPid=" + firstPids.get(i) + "); deadline exceeded."); return; } if (DEBUG_ANR) { Slog.d(TAG, "Done with pid " + firstPids.get(i) + " in " + timeTaken + "ms"); } } } //接下來收集native pid的堆棧 if (nativePids != null) { for (int pid : nativePids) { if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for native pid " + pid); final long nativeDumpTimeoutMs = Math.min(NATIVE_DUMP_TIMEOUT_MS, remainingTime); final long start = SystemClock.elapsedRealtime(); Debug.dumpNativeBacktraceToFileTimeout( pid, tracesFile, (int) (nativeDumpTimeoutMs / 1000)); final long timeTaken = SystemClock.elapsedRealtime() - start; remainingTime -= timeTaken; if (remainingTime <= 0) { Slog.e(TAG, "Aborting stack trace dump (current native pid=" + pid + "); deadline exceeded."); return; } if (DEBUG_ANR) { Slog.d(TAG, "Done with native pid " + pid + " in " + timeTaken + "ms"); } } } // 最后,從CPU跟蹤器轉(zhuǎn)儲所有額外PID的堆棧阎抒。 if (extraPids != null) { for (int pid : extraPids) { if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for extra pid " + pid); final long timeTaken; if (useTombstonedForJavaTraces) { timeTaken = dumpJavaTracesTombstoned(pid, tracesFile, remainingTime); } else { timeTaken = observer.dumpWithTimeout(pid, remainingTime); } remainingTime -= timeTaken; if (remainingTime <= 0) { Slog.e(TAG, "Aborting stack trace dump (current extra pid=" + pid + "); deadline exceeded."); return; } if (DEBUG_ANR) { Slog.d(TAG, "Done with extra pid " + pid + " in " + timeTaken + "ms"); } } } } finally { if (observer != null) { observer.stopWatching(); } } }
- 看完之后酪我,應(yīng)該可以很清楚地的明白。ANR的流程就是打印一些 ANR reason且叁、cpu stats都哭、線程日志,然后分別寫入main.log、event.log欺矫,然后調(diào)用到addErrorToDropBox中纱新,最后kill該進(jìn)程。
07.回過頭看addErrorToDropBox
- 為什么說addErrorToDropBox是殊途同歸呢穆趴,因為無論是crash脸爱、native_crash、ANR或是wtf未妹,最終都是來到這里簿废,交由它去處理。那下面我們就來揭開它的神秘面紗吧络它。
public void addErrorToDropBox(String eventType, ProcessRecord process, String processName, ActivityRecord activity, ActivityRecord parent, String subject, final String report, final File dataFile, final ApplicationErrorReport.CrashInfo crashInfo) { // NOTE -- this must never acquire the ActivityManagerService lock, // otherwise the watchdog may be prevented from resetting the system. // Bail early if not published yet if (ServiceManager.getService(Context.DROPBOX_SERVICE) == null) return; final DropBoxManager dbox = mContext.getSystemService(DropBoxManager.class); //只有這幾種類型的錯誤,才會進(jìn)行上傳 final boolean shouldReport = ("anr".equals(eventType) || "crash".equals(eventType) || "native_crash".equals(eventType) || "watchdog".equals(eventType)); // Exit early if the dropbox isn't configured to accept this report type. final String dropboxTag = processClass(process) + "_" + eventType; //1.如果DropBoxManager沒有初始化,或不是要上傳的類型,則直接返回 if (dbox == null || !dbox.isTagEnabled(dropboxTag)&& !shouldReport) return; ... final StringBuilder sb = new StringBuilder(1024); //2.添加一些頭部log信息 appendDropBoxProcessHeaders(process, processName, sb); //3.添加崩潰進(jìn)程和界面的信息 try { if (process != null) { //添加是否前臺前程log sb.append("Foreground: ") .append(process.isInterestingToUserLocked() ? "Yes" : "No") .append("\n"); } //觸發(fā)該崩潰的界面,可以為null if (activity != null) { sb.append("Activity: ").append(activity.shortComponentName).append("\n"); } if (parent != null && parent.app != null && parent.app.pid != process.pid) { sb.append("Parent-Process: ").append(parent.app.processName).append("\n"); } if (parent != null && parent != activity) { sb.append("Parent-Activity: ").append(parent.shortComponentName).append("\n"); } //定入簡要信息 if (subject != null) { sb.append("Subject: ").append(subject).append("\n"); } sb.append("Build: ").append(Build.FINGERPRINT).append("\n"); //是否連接了調(diào)試 if (Debug.isDebuggerConnected()) { sb.append("Debugger: Connected\n"); } } catch (NullPointerException e) { e.printStackTrace(); } finally { sb.append("\n"); } final String fProcessName = processName; final String fEventType = eventType; final String packageName = getErrorReportPackageName(process, crashInfo, eventType); Slog.i(TAG,"addErrorToDropbox, real report package is "+packageName); // Do the rest in a worker thread to avoid blocking the caller on I/O // (After this point, we shouldn't access AMS internal data structures.) Thread worker = new Thread("Error dump: " + dropboxTag) { @Override public void run() { //4.添加進(jìn)程的狀態(tài)到dropbox中 BufferedReader bufferedReader = null; String line; try { bufferedReader = new BufferedReader(new FileReader("/proc/" + pid + "/status")); for (int i = 0; i < 5; i++) { if ((line = bufferedReader.readLine()) != null && line.contains("State")) { sb.append(line + "\n"); break; } } } catch (IOException e) { e.printStackTrace(); } finally { if (bufferedReader != null) { try { bufferedReader.close(); } catch (IOException e) { e.printStackTrace(); } } } if (report != null) { sb.append(report); } String setting = Settings.Global.ERROR_LOGCAT_PREFIX + dropboxTag; int lines = Settings.Global.getInt(mContext.getContentResolver(), setting, 0); int maxDataFileSize = DROPBOX_MAX_SIZE - sb.length() - lines * RESERVED_BYTES_PER_LOGCAT_LINE; //5.將dataFile文件定入dropbox中,一般只有anr時,會將traces文件通過該參數(shù)傳遞進(jìn)來者,其他類型都不傳. if (dataFile != null && maxDataFileSize > 0) { try { sb.append(FileUtils.readTextFile(dataFile, maxDataFileSize, "\n\n[[TRUNCATED]]")); } catch (IOException e) { Slog.e(TAG, "Error reading " + dataFile, e); } } //6.如果是crash類型,會傳入crashInfo,此時將其寫入dropbox中 if (crashInfo != null && crashInfo.stackTrace != null) { sb.append(crashInfo.stackTrace); } if (lines > 0) { sb.append("\n"); // 7.合并幾個logcat流,取最新部分log InputStreamReader input = null; try { java.lang.Process logcat = new ProcessBuilder( "/system/bin/timeout", "-k", "15s", "10s", "/system/bin/logcat", "-v", "threadtime", "-b", "events", "-b", "system", "-b", "main", "-b", "crash", "-t", String.valueOf(lines)) .redirectErrorStream(true).start(); try { logcat.getOutputStream().close(); } catch (IOException e) {} try { logcat.getErrorStream().close(); } catch (IOException e) {} input = new InputStreamReader(logcat.getInputStream()); int num; char[] buf = new char[8192]; while ((num = input.read(buf)) > 0) sb.append(buf, 0, num); } catch (IOException e) { Slog.e(TAG, "Error running logcat", e); } finally { if (input != null) try { input.close(); } catch (IOException e) {} } } ... if (shouldReport) { synchronized (mErrorListenerLock) { try { if (mIApplicationErrorListener == null) { return; } //8.關(guān)鍵,在這里可以添加一個application error的接口族檬,用來實現(xiàn)應(yīng)用層接收崩潰信息 mIApplicationErrorListener.onError(fEventType, packageName, fProcessName, subject, dropboxTag + "-" + uuid, crashInfo); } catch (DeadObjectException e) { Slog.i(TAG, "ApplicationErrorListener.onError() E :" + e, e); mIApplicationErrorListener = null; } catch (Exception e) { Slog.i(TAG, "ApplicationErrorListener.onError() E :" + e, e); } } } } }; ... }
- 調(diào)用appendDropBoxProcessHeaders添加頭部log信息:
private void appendDropBoxProcessHeaders(ProcessRecord process, String processName, StringBuilder sb) { // Watchdog thread ends up invoking this function (with // a null ProcessRecord) to add the stack file to dropbox. // Do not acquire a lock on this (am) in such cases, as it // could cause a potential deadlock, if and when watchdog // is invoked due to unavailability of lock on am and it // would prevent watchdog from killing system_server. if (process == null) { sb.append("Process: ").append(processName).append("\n"); return; } // Note: ProcessRecord 'process' is guarded by the service // instance. (notably process.pkgList, which could otherwise change // concurrently during execution of this method) synchronized (this) { sb.append("Process: ").append(processName).append("\n"); sb.append("PID: ").append(process.pid).append("\n"); int flags = process.info.flags; IPackageManager pm = AppGlobals.getPackageManager(); //添加該進(jìn)程的flag sb.append("Flags: 0x").append(Integer.toHexString(flags)).append("\n"); for (int ip=0; ip<process.pkgList.size(); ip++) { String pkg = process.pkgList.keyAt(ip); sb.append("Package: ").append(pkg); try { PackageInfo pi = pm.getPackageInfo(pkg, 0, UserHandle.getCallingUserId()); if (pi != null) { sb.append(" v").append(pi.getLongVersionCode()); if (pi.versionName != null) { sb.append(" (").append(pi.versionName).append(")"); } } } catch (RemoteException e) { Slog.e(TAG, "Error getting package info: " + pkg, e); } sb.append("\n"); } //如果是執(zhí)行安裝的app,會在log中添加此項 if (process.info.isInstantApp()) { sb.append("Instant-App: true\n"); } } }