理解 Android ANR 的信息收集过程

基于Android 6.0源码, 分析当发生ANR时系统的处理过程

    frameworks/base/core/java/android/os/Debug.java
    frameworks/base/core/jni/android_os_Debug.cpp
    system/core/libcutils/debugger.c

一. ANR场景

无论是四大组件或者进程等只要发生ANR,最终都会调用AMS.appNotResponding()方法,下面从这个方法说起。

以下场景都会触发调用AMS.appNotResponding方法:

  • Service Timeout:比如前台服务在20s内未执行完成;
  • BroadcastQueue Timeout:比如前台广播在10s内未执行完成
  • InputDispatching Timeout: 输入事件分发超时5s,包括按键和触摸事件。

二. appNotResponding处理流程

1. AMS.appNotResponding

    final void appNotResponding(ProcessRecord app, ActivityRecord activity,
            ActivityRecord parent, boolean aboveSystem, final String annotation) {
        ...
        updateCpuStatsNow(); //第一次 更新cpu统计信息
        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");
        }

        //创建CPU tracker对象
        final ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);
        //输出traces信息【见小节2】
        File tracesFile = dumpStackTraces(true, firstPids, processCpuTracker, 
                lastPids, NATIVE_STACKS_OF_INTEREST);

        updateCpuStatsNow(); //第二次更新cpu统计信息
        //记录当前各个进程的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状态,病查询错误报告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线程发送,内容为SHOW_NOT_RESPONDING_MSG的消息
            mUiHandler.sendMessage(msg);
        }

    }

当发生ANR时, 会按顺序依次执行:

  1. 输出ANR Reason信息到EventLog. 也就是说ANR触发的时间点最接近的就是EventLog中输出的am_anr信息;
  2. 收集并输出重要进程列表中的各个线程的traces信息,该方法较耗时; 【见小节2】
  3. 输出当前各个进程的CPU使用情况以及CPU负载情况;
  4. 将traces文件和 CPU使用情况信息保存到dropbox,即data/system/dropbox目录
  5. 根据进程类型,来决定直接后台杀掉,还是弹框告知用户.

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();
            //创建traces文件
            tracesFile.createNewFile();
            FileUtils.setPermissions(tracesFile.getPath(), 0666, -1, -1);
        } catch (IOException e) {
            return null;
top Created with Sketch.