一. 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时, 会按顺序依次执行:
- 输出ANR Reason信息到EventLog. 也就是说ANR触发的时间点最接近的就是EventLog中输出的am_anr信息;
- 收集并输出重要进程列表中的各个线程的traces信息,该方法较耗时; 【见小节2】
- 输出当前各个进程的CPU使用情况以及CPU负载情况;
- 将traces文件和 CPU使用情况信息保存到dropbox,即data/system/dropbox目录
- 根据进程类型,来决定直接后台杀掉,还是弹框告知用户.
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;
- }
- //输出trace内容【见小节3】
- dumpStackTraces(tracesPath, firstPids, processCpuTracker, lastPids, nativeProcs);
- return tracesFile;
- }
这里会保证data/anr/traces.txt文件内容是全新的方式,而非追加。
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();
-
- //首先,获取最重要进程的stacks
- if (firstPids != null) {
- try {
- int num = firstPids.size();
- for (int i = 0; i < num; i++) {
- synchronized (observer) {
- //向目标进程发送signal来输出traces
- Process.sendSignal(firstPids.get(i), Process.SIGNAL_QUIT);
- observer.wait(200); //等待直到写关闭,或者200ms超时
- }
- }
- } catch (InterruptedException e) {
- Slog.wtf(TAG, e);
- }
- }
-
- //下一步,获取native进程的stacks
- if (nativeProcs != null) {
- int[] pids = Process.getPidsForCommands(nativeProcs);
- if (pids != null) {
- for (int pid : pids) {
- //输出native进程的trace【见小节4】
- Debug.dumpNativeBacktraceToFile(pid, tracesPath);
- }
- }
- }
-
- if (processCpuTracker != null) {
- processCpuTracker.init();
- System.gc();
- processCpuTracker.update();
- synchronized (processCpuTracker) {
- processCpuTracker.wait(500); //等待500ms
- }
- //测量CPU使用情况
- processCpuTracker.update();
-
- //从lastPids中选取CPU使用率 top 5的进程,输出这些进程的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进程的stacks;
第一个是发生ANR进程;
第二个是system_server;
mLruProcesses中所有的persistent进程;
2.收集Native进程的stacks;(dumpNativeBacktraceToFile)
依次是mediaserver,sdcard,surfaceflinger进程;
3.收集lastPids进程的stacks;;
依次输出CPU使用率top 5的进程;
Tips: firstPids列表中的进程, 两个进程之间会休眠200ms, 可见persistent进程越多,则时间越长. top 5进程的traces过程中, 同样是间隔200ms, 另外进程使用情况的收集也是比较耗时.
4. dumpNativeBacktraceToFile
Debug.dumpNativeBacktraceToFile(pid, tracesPath)经过JNI调用如下方法:
- 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 {
- //【见小节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向服务端发送dump backtrace的请求
- 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中读取到服务端发送过来的数据,并写入buffer
- while ((n = TEMP_FAILURE_RETRY(read(sock_fd, buffer, sizeof(buffer)))) > 0) {
- //再将buffer数据输出到traces.txt文件
- if (TEMP_FAILURE_RETRY(write(fd, buffer, n)) != n) {
- result = -1;
- break;
- }
- }
- close(sock_fd);
- return result;
- }
可见,这个过程主要是通过向debuggerd守护进程发送命令DEBUGGER_ACTION_DUMP_BACKTRACE, debuggerd收到该命令,在子进程中调用 dump_backtrace()来输出backtrace。
三. 总结
触发ANR时系统会输出关键信息:(这个较耗时,可能会有10s)
1.将am_anr信息,输出到EventLog.(ANR开始起点看EventLog)
2.获取重要进程trace信息,保存到/data/anr/traces.txt;(会先删除老的文件)
Java进程的traces;
Native进程的traces;
3.ANR reason以及CPU使用情况信息,输出到main log;
4.再将CPU使用情况和进程trace文件信息,再保存到/data/system/dropbox;
整个过程中进程Trace的输出是最为核心的环节,Java和Native进程采用不同的策略,如下:
进程类型 |
trace命令 |
描述 |
Java |
kill -3 [pid] |
不适用于Native进程 |
Native |
debuggerd -b [pid] |
也适用于Java进程 |
说明:kill -3
命令需要虚拟机的支持,所以无法输出Native进程traces.而debuggerd -b [pid]
也可用于Java进程,但信息量远没有kill -3多。 总之,ANR信息最为重要的是dropbox信息,比如system_server_anr。
重要节点:
- 进程名:cat /proc/[pid]/cmdline
- 线程名:cat /proc/[tid]/comm
- Kernel栈:cat /proc/[tid]/stack
- Native栈: 解析 /proc/[pid]/maps
以上就是浅谈Android ANR的信息收集过程的详细内容,更多关于Android ANR 的资料请关注w3xue其它相关文章!