当前位置:   article > 正文

Android之ANR问题分析,触发原理,解决办法_android anr

android anr

 1.ANR是什么?

Application Not Responding,字面意思就是应用无响应,稍加解释就是用户的一些操作无法从应用中获取反馈;在实际的应用中应当去避免这种现象,虽然它暂时不会造成应用崩溃,但是却极大地损坏用户体验;

2.ANR触发原因

出现ANR之后一个直观的现象就是系统会展示出一个ANR对话框,如图:

Android系统中,ActivityManagerService(简称AMS)和WindowManagerService(简称WMS)会检测APP的响应时间,如果APP在特定时间无法处理相应屏幕触摸或键盘输入事件,或者特定时间没有处理完毕,就会出现ANR;

那么哪些场景会造成ANR呢?

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

因此避免以上四种情况就是解决ANR的关键;

导致ANR无响应的常见原因:

  • 主线程阻塞或主线程数据读取

解决办法:避免死锁的出现,使用子线程来处理耗时操作或阻塞任务。尽量避免在主线程query provider、不要滥用SharePreferenceS

  • CPU满负荷,I/O阻塞

解决办法:文件读写或数据库操作放在子线程异步操作。

  • 内存不足

解决办法:AndroidManifest.xml文件<applicatiion>中可以设置 android:largeHeap="true",以此增大App使用内存。不过不建议使用此法,从根本上防止内存泄漏,优化内存使用才是正道。

  • 各大组件ANR

各大组件生命周期中也应避免耗时操作,注意BroadcastReciever的onRecieve()、后台Service和ContentProvider也不要执行太长时间的任务。

3.ANR时系统做了什么?

ANR时展示给用户什么内容,源码分析ANR做了哪些事情,知道ANR做了什么,更有助于我们分析ANR;

3.1展示一个应用无反应的对话框

在Activity内为Button添加点击事件,点击时让主线程休眠30秒,在连续点击几次Button按钮;

  1. Button.setOnClickListener(new View.OnClickListener() {
  2. @Override
  3. public void onClick(View v) {
  4. // 这是Android提供线程休眠函数,与Thread.sleep()最大的区别是
  5. // 该使用该函数不会抛出InterruptedException异常。
  6. SystemClock.sleep(30 * 1000);
  7. }
  8. });

 在点击Button按钮第一次以后,在连续点击几次Button按钮,大概七八秒,终于弹出ANR异常;

3.2将ANR信息输出到Logcat

产生ANR时Logcat会同时如下内容:

10-15 01:47:20.975 4038-4046/com.gome.childrenmanager I/art: Thread[5,tid=4046,WaitingInMainSignalCatcherLoop,Thread*=0xaf00d400,peer=0x12c00080,"Signal Catcher"]: reacting to signal 3
10-15 01:47:20.985 4038-4046/com.gome.childrenmanager I/art: Wrote stack traces to '/data/anr/traces.txt'
10-15 01:47:45.820 4038-4038/com.gome.childrenmanager I/Choreographer: Skipped 42 frames!  The application may be doing too much work on its main thread.

可以看到Logcat清晰的记录ANR发生的时间,以及线程的tid和一句话概括的原因:WaitingInMainSignalCatcherLoop主线程等待异常,最后一句话 Skipped 42 frames!  The application may be doing too much work on its main thread.告知主线程被阻塞导致帧画面无法刷新;

3.3将ANR信息输出到traces.txt文件中

Wrote stack traces to '/data/anr/traces.txt

通过Logcat输出日志看到会将ANR信息写入traces.txt文件,traces.txt文件是一个ANR记录文件,用于开发人员调试分析ANR产生原因,目录在/data/anr中,可以通过adb pull命令导出:

adb pull /data/anr/traces.txt    C:\anr

adb命令默认在Android SDK下platform-tools目录,没有配置adb环境变量的需要进入platform-tools目录执行adb命令;

不指定traces.txt导出默认,默认导出到adb所在命令目录;后面对如何分析traces.txt这个文件做详细描述;

3.4ANR源码分析

最后来看看做出上述反应的源代码,这部分代码位于ProcessRecord类中;

ActivityManagerService监听到ANR信息,调用AnrHelper下的方法appNotResponding(),AnrHelper在后台开启一个独立的线程去处理ANR消息,以便缩短处理时间;

  1. AnrHelper
  2. private class AnrConsumerThread extends Thread {
  3. @Override
  4. public void run() {
  5. AnrRecord r;
  6. r.appNotResponding(onlyDumpSelf);
  7. }
  8. }
  9. private static class AnrRecord {
  10. final ProcessRecord mApp;
  11. void appNotResponding(boolean onlyDumpSelf) {
  12. mApp.appNotResponding(mActivityShortComponentName, mAppInfo,
  13. mParentShortComponentName, mParentProcess, mAboveSystem, mAnnotation,
  14. onlyDumpSelf);
  15. }
  16. }

ProcessRecord.appNotResponding()

  1. ProcessRecord.java
  2. void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,
  3. String parentShortComponentName, WindowProcessController parentProcess,
  4. boolean aboveSystem, String annotation, boolean onlyDumpSelf) {
  5. ArrayList<Integer> firstPids = new ArrayList<>(5);
  6. SparseArray<Boolean> lastPids = new SparseArray<>(20);
  7. mWindowProcessController.appEarlyNotResponding(annotation, () -> kill("anr",
  8. ApplicationExitInfo.REASON_ANR, true));
  9. long anrTime = SystemClock.uptimeMillis();
  10. if (isMonitorCpuUsage()) {
  11. //第一次,更新CPU统计信息
  12. mService.updateCpuStatsNow();
  13. }
  14. final boolean isSilentAnr;
  15. synchronized (mService) {
  16. // 某些特定情况下忽略本次ANR,比如系统关机,比如该进程已经处于anr状态或者crash状态
  17. if (mService.mAtmInternal.isShuttingDown()) {
  18. Slog.i(TAG, "During shutdown skipping ANR: " + this + " " + annotation);
  19. return;
  20. } else if (isNotResponding()) {
  21. Slog.i(TAG, "Skipping duplicate ANR: " + this + " " + annotation);
  22. return;
  23. } else if (isCrashing()) {
  24. Slog.i(TAG, "Crashing app skipping ANR: " + this + " " + annotation);
  25. return;
  26. } else if (killedByAm) {
  27. Slog.i(TAG, "App already killed by AM skipping ANR: " + this + " " + annotation);
  28. return;
  29. } else if (killed) {
  30. Slog.i(TAG, "Skipping died app ANR: " + this + " " + annotation);
  31. return;
  32. }
  33. //为了防止多次对相同APP的anr执行重复代码,在此处标注记录,属于上面的特定情况中的一种
  34. setNotResponding(true);
  35. // 记录ANR信息到Event Log中
  36. EventLog.writeEvent(EventLogTags.AM_ANR, userId, pid, processName, info.flags,
  37. annotation);
  38. //将当前进程添加到firstPids
  39. firstPids.add(pid);
  40. // 如果它是一个后台的ANR或者仅仅请求导出他自己,不需要加入其它PIDS
  41. isSilentAnr = isSilentAnr();
  42. if (!isSilentAnr && !onlyDumpSelf) {
  43. int parentPid = pid;
  44. if (parentProcess != null && parentProcess.getPid() > 0) {
  45. parentPid = parentProcess.getPid();
  46. }
  47. if (parentPid != pid) firstPids.add(parentPid);
  48. //将system_server进程添加到firstPids
  49. if (MY_PID != pid && MY_PID != parentPid) firstPids.add(MY_PID);
  50. // 添加所有进程到firstpids中
  51. for (int i = getLruProcessList().size() - 1; i >= 0; i--) {
  52. ProcessRecord r = getLruProcessList().get(i);
  53. if (r != null && r.thread != null) {
  54. int myPid = r.pid;
  55. if (myPid > 0 && myPid != pid && myPid != parentPid && myPid != MY_PID) {
  56. if (r.isPersistent()) {
  57. firstPids.add(myPid); //将persistent进程添加到firstPids
  58. if (DEBUG_ANR) Slog.i(TAG, "Adding persistent proc: " + r);
  59. } else if (r.treatLikeActivity) {
  60. firstPids.add(myPid);
  61. if (DEBUG_ANR) Slog.i(TAG, "Adding likely IME: " + r);
  62. } else { //其它进程添加到lastPids
  63. lastPids.put(myPid, Boolean.TRUE);
  64. if (DEBUG_ANR) Slog.i(TAG, "Adding ANR proc: " + r);
  65. }
  66. }
  67. }
  68. }
  69. }
  70. }
  71. // 记录ANR输出到main log
  72. StringBuilder info = new StringBuilder();
  73. info.setLength(0);
  74. info.append("ANR in ").append(processName);
  75. if (activityShortComponentName != null) {
  76. info.append(" (").append(activityShortComponentName).append(")");
  77. }
  78. info.append("\n");
  79. info.append("PID: ").append(pid).append("\n");
  80. if (annotation != null) {
  81. info.append("Reason: ").append(annotation).append("\n");
  82. }
  83. if (parentShortComponentName != null
  84. && parentShortComponentName.equals(activityShortComponentName)) {
  85. info.append("Parent: ").append(parentShortComponentName).append("\n");
  86. }
  87. StringBuilder report = new StringBuilder();
  88. report.append(MemoryPressureUtil.currentPsiState());
  89. //创建CPU tracker对象
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/爱喝兽奶帝天荒/article/detail/989649
推荐阅读
相关标签
  

闽ICP备14008679号