[ANR] 发生ANR后的信息采集过程

发生ANR后,系统会为我们提供一些信息,便于我们分析问题,如生成trace文件,在log中打印CPU信息等。

这篇文章,我们来看看ANR发生之后,系统会提供给我们哪些信息,以及这些信息是如何采集和输出的。

系统提供的信息

系统提供给我们的信息,主要有:

  • EventLog中会打印 "am_anr" 的日志
  • MainLog中会打印ANR发生的进程、原因、CPU负载等信息
  • /data/anr路径下会生成一个trace文件,打印出主要进程的堆栈信息
  • dropbox会保存trace文件和CPU负载信息data/system/dropbox目录

采集信息源码

发生 ANR 后,不管是哪种类型的 ANR,系统都会调用到appNotResponding方法中,进行信息的采集工作。

这个方法所在的位置,在不同的Android版本有区别。旧版本是在AMS中,新版本是在ProcessErrorStateRecord类中。

我们以新版本的appNotResponding源码为例,分步进行讲解。

  1. appNotResponding传入参数
 void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,
            String parentShortComponentName, WindowProcessController parentProcess,
            boolean aboveSystem, String annotation, boolean onlyDumpSelf) {
}

其中annotation就是shortMsg,表示ANR发生的原因。

  1. 先获取一次CPU信息
        long anrTime = SystemClock.uptimeMillis();
        if (isMonitorCpuUsage()) {
            mService.updateCpuStatsNow();
        }
  1. 设置annotation
          // Store annotation here as instance above will not be hit on all paths.
            setAnrAnnotation(annotation);
  1. 判断是否需要跳过
            // PowerManager.reboot() can block for a long time, so ignore ANRs while shutting down.
            if (mService.mAtmInternal.isShuttingDown()) {
                Slog.i(TAG, "During shutdown skipping ANR: " + this + " " + annotation);
                return;
            } else if (isNotResponding()) {
                Slog.i(TAG, "Skipping duplicate ANR: " + this + " " + annotation);
                return;
            } else if (isCrashing()) {
                Slog.i(TAG, "Crashing app skipping ANR: " + this + " " + annotation);
                return;
            } else if (mApp.isKilledByAm()) {
                Slog.i(TAG, "App already killed by AM skipping ANR: " + this + " " + annotation);
                return;
            } else if (mApp.isKilled()) {
                Slog.i(TAG, "Skipping died app ANR: " + this + " " + annotation);
                return;
            }

有几种情况会跳过:

  • 如果正在关机中
  • 如果已经被标记为notResponding,正在处理 ANR 中
  • 进程正在 crash 处理中
  • 进程已经被 AMS 杀掉
  • 进程已经被杀
  1. 设置notResponding的标记
setNotResponding(true);
  1. 打印am_anr的EventLog
 EventLog.writeEvent(EventLogTags.AM_ANR, mApp.userId, pid, mApp.processName,
                    mApp.info.flags, annotation);
  1. 获取需要dump的所有进程id
           // 先把当前进程添加到firstPids
            firstPids.add(pid);

            // 如果是silentAnr,则不需要dump其他进程,silentAnr主要是后台anr
            isSilentAnr = isSilentAnr();
            if (!isSilentAnr && !onlyDumpSelf) {
               // 将parentPid加入firstPids
                int parentPid = pid;
                if (parentProcess != null && parentProcess.getPid() > 0) {
                    parentPid = parentProcess.getPid();
                }
                if (parentPid != pid) firstPids.add(parentPid);
               // MY_PID是system_server的pid,将system_server加入firstPids
                if (MY_PID != pid && MY_PID != parentPid) firstPids.add(MY_PID);

                final int ppid = parentPid;
                // 所有进程,按lru的顺序排列
                mService.mProcessList.forEachLruProcessesLOSP(false, r -> {
                    if (r != null && r.getThread() != null) {
                        int myPid = r.getPid();
                        if (myPid > 0 && myPid != pid && myPid != ppid && myPid != MY_PID) {
                            if (r.isPersistent()) {
                                firstPids.add(myPid); // 将persisitent进程加入firstPids
                            } else if (r.mServices.isTreatedLikeActivity()) {
                                firstPids.add(myPid);   // treatedLikeActivity
                            } else {
                                lastPids.put(myPid, Boolean.TRUE);  // 其他进程加入lastPids
                            }
                        }
                    }
                });
            }
        }

加入firstPids的进程:

  • 当前进程
  • parent 进程
  • system_server 进程
  1. 生成mainlog
        StringBuilder info = new StringBuilder();
        info.setLength(0);
        info.append("ANR in ").append(mApp.processName);
        if (activityShortComponentName != null) {
            info.append(" (").append(activityShortComponentName).append(")");
        }
        info.append("\n");
        info.append("PID: ").append(pid).append("\n");
        if (annotation != null) {
            info.append("Reason: ").append(annotation).append("\n");
        }
        if (parentShortComponentName != null
                && parentShortComponentName.equals(activityShortComponentName)) {
            info.append("Parent: ").append(parentShortComponentName).append("\n");
        }
        if (errorId != null) {
            info.append("ErrorId: ").append(errorId.toString()).append("\n");
        }
        info.append("Frozen: ").append(mApp.mOptRecord.isFrozen()).append("\n");

初始化一个StringBuilder对象info,用来记录输出到mainLog里的内容。
主要包含如下内容:

  • ANR in xxx
  • PID: xxx
  • Reason: xxx (shortMsg)
  • Parent: xxx (可能没有)
  • ErrorId: xxx (可能没有)
  • Frozen: 是否frozen
  • CPU信息
  1. 收集需要 dump 的native pids
        String[] nativeProcs = null;
        if (isSilentAnr || onlyDumpSelf) {
            for (int i = 0; i < NATIVE_STACKS_OF_INTEREST.length; i++) {
                // 写死在watchdog中的native进程,主要包含audioserver、cameraserver等。
                if (NATIVE_STACKS_OF_INTEREST[i].equals(mApp.processName)) {
                    nativeProcs = new String[] { mApp.processName };
                    break;
                }
            }
        } else {
            nativeProcs = NATIVE_STACKS_OF_INTEREST;
        }

        int[] pids = nativeProcs == null ? null : Process.getPidsForCommands(nativeProcs);
        ArrayList<Integer> nativePids = null;
        if (pids != null) {
            nativePids = new ArrayList<>(pids.length);
            for (int i : pids) {
                nativePids.add(i);
            }
        }

NATIVE_STACKS_OF_INTEREST写死在WatchDog.java文件,主要包含audioservercameraserver等。

  1. 开始dump trace文件
       // 生成ProcessCpuTracker
       ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);
       File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,
                isSilentAnr ? null : processCpuTracker, isSilentAnr ? null : lastPids,
                nativePids, tracesFileException, offsets, annotation, criticalEventLog);

dump trace文件的具体细节,以及SignalCatcher线程监听信号,下一篇文章再详细讲。

  1. 再次获取CPU信息,并打印mainLog
       if (isMonitorCpuUsage()) {
            mService.updateCpuStatsNow();
            mService.mAppProfiler.printCurrentCpuState(report, anrTime);
            info.append(processCpuTracker.printCurrentLoad());
            info.append(report);
        }
        info.append(processCpuTracker.printCurrentState(anrTime));
        // 打印mainLog,tag为ActivityManager
        Slog.e(TAG, info.toString());
  1. 保存到 dropbox
        mService.addErrorToDropBox("anr", mApp, mApp.processName, activityShortComponentName,
                parentShortComponentName, parentPr, null, report.toString(), tracesFile,
                null, new Float(loadingProgress), incrementalMetrics, errorId);

把traces文件、CPU使用率等信息,保存到dropbox,即data/system/dropbox目录

  1. 如果是后台 ANR,直接杀进程
            if (isSilentAnr() && !mApp.isDebugging()) {
                mApp.killLocked("bg anr", ApplicationExitInfo.REASON_ANR, true);
                return;
            }
  1. 设置AppnotRespondingReport(到这里,AMS 才能查询到进程是否发生 ANR)
            synchronized (mProcLock) {
                // 设置AppNotRespondingReport
                makeAppNotRespondingLSP(activityShortComponentName,
                        annotation != null ? "ANR " + annotation : "ANR", info.toString());
                mDialogController.setAnrController(anrController);
            }
    private void makeAppNotRespondingLSP(String activity, String shortMsg, String longMsg) {
        setNotResponding(true);
        if (mService.mAppErrors != null) {
            // 设置NotRespondingReport
            mNotRespondingReport = mService.mAppErrors.generateProcessError(mApp,
                    ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING,
                    activity, shortMsg, longMsg, null);
        }
        startAppProblemLSP();
        mApp.getWindowProcessController().stopFreezingActivities();
    }
  1. 唤醒 ANR 弹窗
            if (mService.mUiHandler != null) {
                // 唤醒AppNotResponding弹窗
                Message msg = Message.obtain();
                msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG;
                msg.obj = new AppNotRespondingDialog.Data(mApp, aInfo, aboveSystem);
                mService.mUiHandler.sendMessageDelayed(msg, anrDialogDelayMs);
            }

到这里,AppNotResponding的流程就讲完了。

从AMS获取App的ErrorState

AMS提供一个public的接口,用于查询所有进程的ErrorState

    public List<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState() {
        synchronized (mProcLock) {
            // 遍历所有进程
            mProcessList.forEachLruProcessesLOSP(false, app -> {
                // 获取进程的mErrorState
                final ProcessErrorStateRecord errState = app.mErrorState;
                final boolean crashing = errState.isCrashing();
                final boolean notResponding = errState.isNotResponding();
                if ((app.getThread() != null) && (crashing || notResponding)) {
                    ActivityManager.ProcessErrorStateInfo report = null;
                    if (crashing) {
                       // 如果是crashing,需要获取CrashingReport
                        report = errState.getCrashingReport();
                    } else if (notResponding) {
                       // 如果是notResponding,需要获取notRespondingReport
                        report = errState.getNotRespondingReport();
                    }

                    if (report != null) {
                        if (errList[0] == null) {
                            errList[0] = new ArrayList<>(1);
                        }
                        errList[0].add(report);
                    } else {
                        Slog.w(TAG, "Missing app error report, app = " + app.processName +
                                " crashing = " + crashing +
                                " notResponding = " + notResponding);
                    }
                }
            });
        }
        return errList[0];
    }

这个方法的作用,主要是找到出现 Crash 或 ANR 的进程列表。可以通过循环调用该方法,判断进程是否发生 ANR。

不过这个判断不会很准,因为只有当发生 ANR 的进程的notRespondingReport生成后,才会返回该进程。由前面的分析克制,生成notRespondingReport的时机,是在dump trace完成之后,弹出ANR弹窗之前。

以下几种情况会导致我们无法获取到进程的ErrorState

  • 用户可能在我们调用方法之间,杀掉进程
  • 对于oppo和vivo手机,发生ANR后会自动杀死进程,几乎没办法拿到

总结

当 ANR 发生时,系统会调用appNotResponding方法,修改进程的ErrorState状态,同时dump丰富的信息。

主要流程如下:

  1. am_anr信息,输出到EventLog(这是ANR发生的起点)
  2. 获取重要进程的trace信息,保存到/data/anr/traces.txt
    • Java进程:当前进程、parent进程、system_server、top 5的进程
    • Native进程:audioservercameraserver
  3. ANR reasonCPU使用情况,输出到 MainLog
  4. CPU使用情况及traces文件信息,保存到/data/system/dropbox
  5. 如果是后台ANR,直接杀进程
  6. 如果是前台ANR,设置notRespondingReport,激活 ANR 弹窗
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容