深入学习Android ANR 的原理分析及解决办法

目录
  • 一、ANR说明和原因
    • 1.1 简介
    • 1.2 原因
    • 1.3 避免
  • 二、ANR分析办法
    • 2.1 ANR重现
    • 2.2 ANR分析办法一:Log
    • 2.3 ANR分析办法二:traces.txt
    • 2.4 ANR分析办法三:Java线程调用分析
    • 2.5 ANR分析办法四:DDMS分析ANR问题
  • 三、造成ANR的原因及解决办法
    • 四、ANR源码分析
      • 4.1 Service造成的Service Timeout
      • 4.2 BroadcastReceiver造成的BroadcastQueue Timeout
      • 4.3 ContentProvider的ContentProvider Timeout
    • 五、Android ANR的信息收集

      一、ANR说明和原因

      1.1 简介

      ANR全称:Application Not Responding,也就是应用程序无响应。

      1.2 原因

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

      以下四个条件都可以造成ANR发生:

      • InputDispatching Timeout:5秒内无法响应屏幕触摸事件或键盘输入事件
      • BroadcastQueue Timeout :在执行前台广播(BroadcastReceiver)的onReceive()函数时10秒没有处理完成,后台为60秒。
      • Service Timeout :前台服务20秒内,后台服务在200秒内没有执行完毕。
      • ContentProvider Timeout :ContentProvider的publish在10s内没进行完。

      1.3 避免

      尽量避免在主线程(UI线程)中作耗时操作。

      那么耗时操作就放在子线程中。

      二、ANR分析办法

      2.1 ANR重现

      这里使用的是号称Google亲儿子的Google Pixel xl(Android 8.0系统)做的测试,生成一个按钮跳转到ANRTestActivity,在后者的onCreate()中主线程休眠20秒:

      @Override
      protected void onCreate(@Nullable Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_anr_test);
          // 这是Android提供线程休眠函数,与Thread.sleep()最大的区别是
          // 该使用该函数不会抛出InterruptedException异常。
          SystemClock.sleep(20 * 1000);
      }
      

      在进入ANRTestActivity后黑屏一段时间,大概有七八秒,终于弹出了ANR异常。

      2.2 ANR分析办法一:Log

      刚才产生ANR后,看下Log:

      可以看到logcat清晰地记录了ANR发生的时间,以及线程的tid和一句话概括原因:WaitingInMainSignalCatcherLoop,大概意思为主线程等待异常。

      最后一句The application may be doing too much work on its main thread.告知可能在主线程做了太多的工作。

      2.3 ANR分析办法二:traces.txt

      刚才的log有第二句Wrote stack traces to '/data/anr/traces.txt',说明ANR异常已经输出到traces.txt文件,使用adb命令把这个文件从手机里导出来:

      1.cd到adb.exe所在的目录,也就是Android SDK的platform-tools目录,例如:

      cd D:\Android\AndroidSdk\platform-tools
      

      此外,除了Windows的cmd以外,还可以使用AndroidStudio的Terminal来输入adb命令。

      2.到指定目录后执行以下adb命令导出traces.txt文件:

      adb pull /data/anr/traces.txt
      

      traces.txt默认会被导出到Android SDK的\platform-tools目录。一般来说traces.txt文件记录的东西会比较多,分析的时候需要有针对性地去找相关记录。

      ----- pid 23346 at 2017-11-07 11:33:57 -----  ----> 进程id和ANR产生时间
      Cmd line: com.sky.myjavatest
      Build fingerprint: 'google/marlin/marlin:8.0.0/OPR3.170623.007/4286350:user/release-keys'
      ABI: 'arm64'
      Build type: optimized
      Zygote loaded classes=4681 post zygote classes=106
      Intern table: 42675 strong; 137 weak
      JNI: CheckJNI is on; globals=526 (plus 22 weak)
      Libraries: /system/lib64/libandroid.so /system/lib64/libcompiler_rt.so 
      /system/lib64/libjavacrypto.so
      /system/lib64/libjnigraphics.so /system/lib64/libmedia_jni.so /system/lib64/libsoundpool.so
      /system/lib64/libwebviewchromium_loader.so libjavacore.so libopenjdk.so (9)
      Heap: 22% free, 1478KB/1896KB; 21881 objects    ----> 内存使用情况
      
      ...
      
      "main" prio=5 tid=1 Sleeping    ----> 原因为Sleeping
        | group="main" sCount=1 dsCount=0 flags=1 obj=0x733d0670 self=0x74a4abea00
        | sysTid=23346 nice=-10 cgrp=default sched=0/0 handle=0x74a91ab9b0
        | state=S schedstat=( 391462128 82838177 354 ) utm=33 stm=4 core=3 HZ=100
        | stack=0x7fe6fac000-0x7fe6fae000 stackSize=8MB
        | held mutexes=
        at java.lang.Thread.sleep(Native method)
        - sleeping on <0x053fd2c2> (a java.lang.Object)
        at java.lang.Thread.sleep(Thread.java:373)
        - locked <0x053fd2c2> (a java.lang.Object)
        at java.lang.Thread.sleep(Thread.java:314)
        at android.os.SystemClock.sleep(SystemClock.java:122)
        at com.sky.myjavatest.ANRTestActivity.onCreate(ANRTestActivity.java:20) ----> 产生ANR的包名以及具体行数
        at android.app.Activity.performCreate(Activity.java:6975)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1213)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2770)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2892)
        at android.app.ActivityThread.-wrap11(ActivityThread.java:-1)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1593)
        at android.os.Handler.dispatchMessage(Handler.java:105)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6541)
        at java.lang.reflect.Method.invoke(Native method)
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
      

      在文件中使用 ctrl + F 查找包名可以快速定位相关代码。

      • 通过上方log可以看出相关问题:
      • 进程id和包名:pid 23346 com.sky.myjavatest
      • 造成ANR的原因:Sleeping
      • 造成ANR的具体行数:ANRTestActivity.java:20类的第20行

      特别注意:产生新的ANR,原来的 traces.txt 文件会被覆盖。

      2.4 ANR分析办法三:Java线程调用分析

      通过JDK提供的命令可以帮助分析和调试Java应用,命令为:

      jstack {pid}
      

      其中pid可以通过jps命令获得,jps命令会列出当前系统中运行的所有Java虚拟机进程,比如

      7266 Test
      7267 Jps
      

      2.5 ANR分析办法四:DDMS分析ANR问题

      • 使用DDMS——Update Threads工具
      • 阅读Update Threads的输出

      三、造成ANR的原因及解决办法

      上面例子只是由于简单的主线程耗时操作造成的ANR,造成ANR的原因还有很多:

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

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

      CPU满负荷,I/O阻塞

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

      内存不足

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

      各大组件ANR

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

      四、ANR源码分析

      4.1 Service造成的Service Timeout

      Service Timeout是位于"ActivityManager"线程中的AMS.MainHandler收到SERVICE_TIMEOUT_MSG消息时触发。

      4.1.1 发送延时消息

      Service进程attach到system_server进程的过程中会调用realStartServiceLocked,紧接着mAm.mHandler.sendMessageAtTime()来发送一个延时消息,延时的时常是定义好的,如前台Service的20秒。ActivityManager线程中的AMS.MainHandler收到SERVICE_TIMEOUT_MSG消息时会触发。

      AS.realStartServiceLocked

      ActiveServices.java

      private final void realStartServiceLocked(ServiceRecord r,
              ProcessRecord app, boolean execInFg) throws RemoteException {
          ...
          //发送delay消息(SERVICE_TIMEOUT_MSG)
          bumpServiceExecutingLocked(r, execInFg, "create");
          try {
              ...
              //最终执行服务的onCreate()方法
              app.thread.scheduleCreateService(r, r.serviceInfo,
                      mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
                      app.repProcState);
          } catch (DeadObjectException e) {
              mAm.appDiedLocked(app);
              throw e;
          } finally {
              ...
          }
      }
      

      AS.bumpServiceExecutingLocked

      private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) {
          ... 
          scheduleServiceTimeoutLocked(r.app);
      }
      
      void scheduleServiceTimeoutLocked(ProcessRecord proc) {
          if (proc.executingServices.size() == 0 || proc.thread == null) {
              return;
          }
          long now = SystemClock.uptimeMillis();
          Message msg = mAm.mHandler.obtainMessage(
                  ActivityManagerService.SERVICE_TIMEOUT_MSG);
          msg.obj = proc;
      
          //当超时后仍没有remove该SERVICE_TIMEOUT_MSG消息,则执行service Timeout流程
          mAm.mHandler.sendMessageAtTime(msg,
              proc.execServicesFg ? (now+SERVICE_TIMEOUT) : (now+ SERVICE_BACKGROUND_TIMEOUT));
      }
      

      4.1.2 进入目标进程的主线程创建Service

      经过Binder等层层调用进入目标进程的主线程 handleCreateService(CreateServiceData data)。

      ActivityThread.java

         private void handleCreateService(CreateServiceData data) {
              ...
              java.lang.ClassLoader cl = packageInfo.getClassLoader();
              Service service = (Service) cl.loadClass(data.info.name).newInstance();
              ...
      
              try {
                  //创建ContextImpl对象
                  ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
                  context.setOuterContext(service);
                  //创建Application对象
                  Application app = packageInfo.makeApplication(false, mInstrumentation);
                  service.attach(context, this, data.info.name, data.token, app,
                          ActivityManagerNative.getDefault());
                  //调用服务onCreate()方法 
                  service.onCreate();
      
                  //取消AMS.MainHandler的延时消息
                  ActivityManagerNative.getDefault().serviceDoneExecuting(
                          data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
              } catch (Exception e) {
                  ...
              }
          }
      

      这个方法中会创建目标服务对象,以及回调常用的Service的onCreate()方法,紧接着通过serviceDoneExecuting()回到system_server执行取消AMS.MainHandler的延时消息。

      4.1.3 回到system_server执行取消AMS.MainHandler的延时消息

      AS.serviceDoneExecutingLocked

      private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
                  boolean finishing) {
          ...
          if (r.executeNesting <= 0) {
              if (r.app != null) {
                  r.app.execServicesFg = false;
                  r.app.executingServices.remove(r);
                  if (r.app.executingServices.size() == 0) {
                      //当前服务所在进程中没有正在执行的service
                      mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
              ...
          }
          ...
      }
      

      此方法中Service逻辑处理完成则移除之前延时的消息SERVICE_TIMEOUT_MSG。如果没有执行完毕不调用这个方法,则超时后会发出SERVICE_TIMEOUT_MSG来告知ANR发生。

      4.2 BroadcastReceiver造成的BroadcastQueue Timeout

      BroadcastReceiver Timeout是位于"ActivityManager"线程中的BroadcastQueue.BroadcastHandler收到BROADCAST_TIMEOUT_MSG消息时触发。

      4.2.1 处理广播函数 processNextBroadcast() 中 broadcastTimeoutLocked(false) 发送延时消息

      广播处理顺序为先处理并行广播,再处理当前有序广播。

      final void processNextBroadcast(boolean fromMsg) {
          synchronized(mService) {
              ...
              // 处理当前有序广播
              do {
                  r = mOrderedBroadcasts.get(0);
                  //获取所有该广播所有的接收者
                  int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
                  if (mService.mProcessesReady && r.dispatchTime > 0) {
                      long now = SystemClock.uptimeMillis();
                      if ((numReceivers > 0) &&
                              (now > r.dispatchTime + (2*mTimeoutPeriod*numReceivers))) {
                          //step 1\. 发送延时消息,这个函数处理了很多事情,比如广播处理超时结束广播
                          broadcastTimeoutLocked(false);
                          ...
                      }
                  }
                  if (r.receivers == null || r.nextReceiver >= numReceivers
                          || r.resultAbort || forceReceive) {
                      if (r.resultTo != null) {
                          //2\. 处理广播消息消息
                          performReceiveLocked(r.callerApp, r.resultTo,
                              new Intent(r.intent), r.resultCode,
                              r.resultData, r.resultExtras, false, false, r.userId);
                          r.resultTo = null;
                      }
                      //3\. 取消广播超时ANR消息
                      cancelBroadcastTimeoutLocked();
                  }
              } while (r == null);
              ...
      
              // 获取下条有序广播
              r.receiverTime = SystemClock.uptimeMillis();
              if (!mPendingBroadcastTimeoutMessage) {
                  long timeoutTime = r.receiverTime + mTimeoutPeriod;
                  //设置广播超时
                  setBroadcastTimeoutLocked(timeoutTime);
              }
              ...
          }
      }
      

      上文的step 1. broadcastTimeoutLocked(false)函数:记录时间信息并调用函数设置发送延时消息

      final void broadcastTimeoutLocked(boolean fromMsg) {
          ...
              long now = SystemClock.uptimeMillis();
              if (fromMsg) {
                  if (mService.mDidDexOpt) {
                      // Delay timeouts until dexopt finishes.
                      mService.mDidDexOpt = false;
                      long timeoutTime = SystemClock.uptimeMillis() + mTimeoutPeriod;
                      setBroadcastTimeoutLocked(timeoutTime);
                      return;
                  }
                  if (!mService.mProcessesReady) {
                      return;
                  }
      
                  long timeoutTime = r.receiverTime + mTimeoutPeriod;
                  if (timeoutTime > now) {
                      // step 2
                      setBroadcastTimeoutLocked(timeoutTime);
                      return;
                  }
              }
      

      上文的step 2.setBroadcastTimeoutLocked函数: 设置广播超时具体操作,同样是发送延时消息

      final void setBroadcastTimeoutLocked(long timeoutTime) {
          if (! mPendingBroadcastTimeoutMessage) {
              Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);
              mHandler.sendMessageAtTime(msg, timeoutTime);
              mPendingBroadcastTimeoutMessage = true;
          }
      }
      

      4.2.2 setBroadcastTimeoutLocked(long timeoutTime)函数的参数timeoutTime是当前时间加上设定好的超时时间。

      也就是上文的

      long timeoutTime = SystemClock.uptimeMillis() + mTimeoutPeriod;
      

      mTimeoutPeriod 也就是前台队列的10s和后台队列的60s。

      public ActivityManagerService(Context systemContext) {
          ...
          static final int BROADCAST_FG_TIMEOUT = 10 * 1000;
          static final int BROADCAST_BG_TIMEOUT = 60 * 1000;
          ...
          mFgBroadcastQueue = new BroadcastQueue(this, mHandler,
                  "foreground", BROADCAST_FG_TIMEOUT, false);
          mBgBroadcastQueue = new BroadcastQueue(this, mHandler,
                  "background", BROADCAST_BG_TIMEOUT, true);
          ...
      }
      

      4.2.3 在processNextBroadcast()过程,执行完performReceiveLocked后调用cancelBroadcastTimeoutLocked

      cancelBroadcastTimeoutLocked :处理广播消息函数 processNextBroadcast() 中 performReceiveLocked() 处理广播消息完毕则调用 cancelBroadcastTimeoutLocked() 取消超时消息。

      final void cancelBroadcastTimeoutLocked() {
          if (mPendingBroadcastTimeoutMessage) {
              mHandler.removeMessages(BROADCAST_TIMEOUT_MSG, this);
              mPendingBroadcastTimeoutMessage = false;
          }
      }
      

      4.3 ContentProvider的ContentProvider Timeout

      ContentProvider Timeout是位于”ActivityManager”线程中的AMS.MainHandler收到CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG消息时触发。

      五、Android ANR的信息收集

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

      参考:理解Android ANR的信息收集过程

      以上就是深入学习Android ANR 的原理分析及解决办法的详细内容,更多关于Android ANR的资料请关注其它相关文章!

      本文转自网络,如有侵权请联系客服删除。