02.Android崩潰Crash庫之App崩潰分析

目錄總結(jié)

  • 01.拋出異常導(dǎo)致崩潰分析
  • 02.RuntimeInit類分析
  • 03.Looper停止App就退出嗎
  • 04.handleApplicationCrash
  • 05.native_crash如何監(jiān)控
  • 06.ANR是如何監(jiān)控的
  • 07.回過頭看addErrorToDropBox

前沿

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");
            }
        }
    }
    

項目地址:https://github.com/yangchong211/YCAndroidTool

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市酪耕,隨后出現(xiàn)的幾起案子导梆,更是在濱河造成了極大的恐慌,老刑警劉巖迂烁,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件看尼,死亡現(xiàn)場離奇詭異,居然都是意外死亡盟步,警方通過查閱死者的電腦和手機藏斩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來却盘,“玉大人狰域,你說我怎么就攤上這事』崎伲” “怎么了兆览?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長塞关。 經(jīng)常有香客問我抬探,道長,這世上最難降的妖魔是什么帆赢? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任小压,我火速辦了婚禮,結(jié)果婚禮上椰于,老公的妹妹穿的比我還像新娘怠益。我一直安慰自己,他們只是感情好瘾婿,可當(dāng)我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布蜻牢。 她就那樣靜靜地躺著烤咧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪孩饼。 梳的紋絲不亂的頭發(fā)上髓削,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天,我揣著相機與錄音镀娶,去河邊找鬼。 笑死揪罕,一個胖子當(dāng)著我的面吹牛梯码,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播好啰,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼轩娶,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了框往?” 一聲冷哼從身側(cè)響起鳄抒,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎椰弊,沒想到半個月后许溅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡秉版,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年贤重,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片清焕。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡并蝗,死狀恐怖秸妥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情粥惧,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布影晓,位于F島的核電站,受9級特大地震影響挂签,放射性物質(zhì)發(fā)生泄漏疤祭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一饵婆、第九天 我趴在偏房一處隱蔽的房頂上張望勺馆。 院中可真熱鬧,春花似錦、人聲如沸草穆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽悲柱。三九已至锋喜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間豌鸡,已是汗流浹背嘿般。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留涯冠,地道東北人炉奴。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像蛇更,于是被迫代替她去往敵國和親瞻赶。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,976評論 2 355