经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 移动开发 » Android » 查看文章
浅谈Android ANR的信息收集过程
来源:jb51  时间:2021/11/24 19:10:34  对本文有异议

一. ANR场景

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

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

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

二. appNotResponding处理流程

1. AMS.appNotResponding

  1. final void appNotResponding(ProcessRecord app, ActivityRecord activity, ActivityRecord parent, boolean aboveSystem, final String annotation) {
  2. ...
  3. updateCpuStatsNow(); //第一次 更新cpu统计信息
  4. synchronized (this) {
  5. //PowerManager.reboot() 会阻塞很长时间,因此忽略关机时的ANR
  6. if (mShuttingDown) {
  7. return;
  8. } else if (app.notResponding) {
  9. return;
  10. } else if (app.crashing) {
  11. return;
  12. }
  13. //记录ANR到EventLog
  14. EventLog.writeEvent(EventLogTags.AM_ANR, app.userId, app.pid,
  15. app.processName, app.info.flags, annotation);
  16. // 将当前进程添加到firstPids
  17. firstPids.add(app.pid);
  18. int parentPid = app.pid;
  19. //将system_server进程添加到firstPids
  20. if (MY_PID != app.pid && MY_PID != parentPid) firstPids.add(MY_PID);
  21. for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
  22. ProcessRecord r = mLruProcesses.get(i);
  23. if (r != null && r.thread != null) {
  24. int pid = r.pid;
  25. if (pid > 0 && pid != app.pid && pid != parentPid && pid != MY_PID) {
  26. if (r.persistent) {
  27. firstPids.add(pid); //将persistent进程添加到firstPids
  28. } else {
  29. lastPids.put(pid, Boolean.TRUE); //其他进程添加到lastPids
  30. }
  31. }
  32. }
  33. }
  34. }
  35. // 记录ANR输出到main log
  36. StringBuilder info = new StringBuilder();
  37. info.setLength(0);
  38. info.append("ANR in ").append(app.processName);
  39. if (activity != null && activity.shortComponentName != null) {
  40. info.append(" (").append(activity.shortComponentName).append(")");
  41. }
  42. info.append("\n");
  43. info.append("PID: ").append(app.pid).append("\n");
  44. if (annotation != null) {
  45. info.append("Reason: ").append(annotation).append("\n");
  46. }
  47. if (parent != null && parent != activity) {
  48. info.append("Parent: ").append(parent.shortComponentName).append("\n");
  49. }
  50. //创建CPU tracker对象
  51. final ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);
  52. //输出traces信息【见小节2】
  53. File tracesFile = dumpStackTraces(true, firstPids, processCpuTracker,
  54. lastPids, NATIVE_STACKS_OF_INTEREST);
  55. updateCpuStatsNow(); //第二次更新cpu统计信息
  56. //记录当前各个进程的CPU使用情况
  57. synchronized (mProcessCpuTracker) {
  58. cpuInfo = mProcessCpuTracker.printCurrentState(anrTime);
  59. }
  60. //记录当前CPU负载情况
  61. info.append(processCpuTracker.printCurrentLoad());
  62. info.append(cpuInfo);
  63. //记录从anr时间开始的Cpu使用情况
  64. info.append(processCpuTracker.printCurrentState(anrTime));
  65. //输出当前ANR的reason,以及CPU使用率、负载信息
  66. Slog.e(TAG, info.toString());
  67. //将traces文件 和 CPU使用率信息保存到dropbox,即data/system/dropbox目录
  68. addErrorToDropBox("anr", app, app.processName, activity, parent, annotation,
  69. cpuInfo, tracesFile, null);
  70.  
  71. synchronized (this) {
  72. ...
  73. //后台ANR的情况, 则直接杀掉
  74. if (!showBackground && !app.isInterestingToUserLocked() && app.pid != MY_PID) {
  75. app.kill("bg anr", true);
  76. return;
  77. }
  78.  
  79. //设置app的ANR状态,病查询错误报告receiver
  80. makeAppNotRespondingLocked(app,
  81. activity != null ? activity.shortComponentName : null,
  82. annotation != null ? "ANR " + annotation : "ANR",
  83. info.toString());
  84.  
  85. //重命名trace文件
  86. String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null);
  87. if (tracesPath != null && tracesPath.length() != 0) {
  88. //traceRenameFile = "/data/anr/traces.txt"
  89. File traceRenameFile = new File(tracesPath);
  90. String newTracesPath;
  91. int lpos = tracesPath.lastIndexOf (".");
  92. if (-1 != lpos)
  93. // 新的traces文件= /data/anr/traces_进程名_当前日期.txt
  94. newTracesPath = tracesPath.substring (0, lpos) + "_" + app.processName + "_" + mTraceDateFormat.format(new Date()) + tracesPath.substring (lpos);
  95. else
  96. newTracesPath = tracesPath + "_" + app.processName;
  97.  
  98. traceRenameFile.renameTo(new File(newTracesPath));
  99. }
  100. //弹出ANR对话框
  101. Message msg = Message.obtain();
  102. HashMap<String, Object> map = new HashMap<String, Object>();
  103. msg.what = SHOW_NOT_RESPONDING_MSG;
  104. msg.obj = map;
  105. msg.arg1 = aboveSystem ? 1 : 0;
  106. map.put("app", app);
  107. if (activity != null) {
  108. map.put("activity", activity);
  109. }
  110. //向ui线程发送,内容为SHOW_NOT_RESPONDING_MSG的消息
  111. mUiHandler.sendMessage(msg);
  112. }
  113. }

当发生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

  1. public static File dumpStackTraces(boolean clearTraces, ArrayList<Integer> firstPids, ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, String[] nativeProcs) {
  2. //默认为 data/anr/traces.txt
  3. String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null);
  4. if (tracesPath == null || tracesPath.length() == 0) {
  5. return null;
  6. }
  7.  
  8. File tracesFile = new File(tracesPath);
  9. try {
  10. //当clearTraces,则删除已存在的traces文件
  11. if (clearTraces && tracesFile.exists()) tracesFile.delete();
  12. //创建traces文件
  13. tracesFile.createNewFile();
  14. FileUtils.setPermissions(tracesFile.getPath(), 0666, -1, -1);
  15. } catch (IOException e) {
  16. return null;
  17. }
  18. //输出trace内容【见小节3】
  19. dumpStackTraces(tracesPath, firstPids, processCpuTracker, lastPids, nativeProcs);
  20. return tracesFile;
  21. }

这里会保证data/anr/traces.txt文件内容是全新的方式,而非追加。

3. AMS.dumpStackTraces

  1. private static void dumpStackTraces(String tracesPath, ArrayList<Integer> firstPids, ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, String[] nativeProcs) {
  2. FileObserver observer = new FileObserver(tracesPath, FileObserver.CLOSE_WRITE) {
  3. @Override
  4. public synchronized void onEvent(int event, String path) { notify(); }
  5. };
  6.  
  7. try {
  8. observer.startWatching();
  9.  
  10. //首先,获取最重要进程的stacks
  11. if (firstPids != null) {
  12. try {
  13. int num = firstPids.size();
  14. for (int i = 0; i < num; i++) {
  15. synchronized (observer) {
  16. //向目标进程发送signal来输出traces
  17. Process.sendSignal(firstPids.get(i), Process.SIGNAL_QUIT);
  18. observer.wait(200); //等待直到写关闭,或者200ms超时
  19. }
  20. }
  21. } catch (InterruptedException e) {
  22. Slog.wtf(TAG, e);
  23. }
  24. }
  25.  
  26. //下一步,获取native进程的stacks
  27. if (nativeProcs != null) {
  28. int[] pids = Process.getPidsForCommands(nativeProcs);
  29. if (pids != null) {
  30. for (int pid : pids) {
  31. //输出native进程的trace【见小节4】
  32. Debug.dumpNativeBacktraceToFile(pid, tracesPath);
  33. }
  34. }
  35. }
  36.  
  37. if (processCpuTracker != null) {
  38. processCpuTracker.init();
  39. System.gc();
  40. processCpuTracker.update();
  41. synchronized (processCpuTracker) {
  42. processCpuTracker.wait(500); //等待500ms
  43. }
  44. //测量CPU使用情况
  45. processCpuTracker.update();
  46.  
  47. //从lastPids中选取CPU使用率 top 5的进程,输出这些进程的stacks
  48. final int N = processCpuTracker.countWorkingStats();
  49. int numProcs = 0;
  50. for (int i=0; i<N && numProcs<5; i++) {
  51. ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
  52. if (lastPids.indexOfKey(stats.pid) >= 0) {
  53. numProcs++;
  54. synchronized (observer) {
  55. Process.sendSignal(stats.pid, Process.SIGNAL_QUIT);
  56. observer.wait(200);
  57. }
  58. }
  59. }
  60. }
  61. } finally {
  62. observer.stopWatching();
  63. }
  64. }

该方法的主要功能,依次输出:

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调用如下方法:

  1. static void android_os_Debug_dumpNativeBacktraceToFile(JNIEnv* env, jobject clazz, jint pid, jstring fileName) {
  2. ...
  3. const jchar* str = env->GetStringCritical(fileName, 0);
  4. String8 fileName8;
  5. if (str) {
  6. fileName8 = String8(reinterpret_cast<const char16_t*>(str),
  7. env->GetStringLength(fileName));
  8. env->ReleaseStringCritical(fileName, str);
  9. }
  10.  
  11. //打开/data/anr/traces.txt
  12. int fd = open(fileName8.string(), O_CREAT | O_WRONLY | O_NOFOLLOW, 0666); /* -rw-rw-rw- */
  13. ...
  14. if (lseek(fd, 0, SEEK_END) < 0) {
  15. fprintf(stderr, "lseek: %s\n", strerror(errno));
  16. } else {
  17. //【见小节5】
  18. dump_backtrace_to_file(pid, fd);
  19. }
  20.  
  21. close(fd);
  22. }

5. dump_backtrace_to_file

[-> debugger.c]

  1. int dump_backtrace_to_file(pid_t tid, int fd) {
  2. return dump_backtrace_to_file_timeout(tid, fd, 0);
  3. }
  4.  
  5. int dump_backtrace_to_file_timeout(pid_t tid, int fd, int timeout_secs) {
  6. //通过socket向服务端发送dump backtrace的请求
  7. int sock_fd = make_dump_request(DEBUGGER_ACTION_DUMP_BACKTRACE, tid, timeout_secs);
  8. if (sock_fd < 0) {
  9. return -1;
  10. }
  11.  
  12. int result = 0;
  13. char buffer[1024];
  14. ssize_t n;
  15. //阻塞等待,从sock_fd中读取到服务端发送过来的数据,并写入buffer
  16. while ((n = TEMP_FAILURE_RETRY(read(sock_fd, buffer, sizeof(buffer)))) > 0) {
  17. //再将buffer数据输出到traces.txt文件
  18. if (TEMP_FAILURE_RETRY(write(fd, buffer, n)) != n) {
  19. result = -1;
  20. break;
  21. }
  22. }
  23. close(sock_fd);
  24. return result;
  25. }

可见,这个过程主要是通过向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其它相关文章!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号