ANR问题的检测、诊断及修复

一. ANR简介

  1. 描述:UI线程如果停止响应太长的时间, “Application Not Responding” (ANR) 就被触发。

    • 如果被阻塞的app处于前台,系统会显示一个ANR对话框。
  2. 触发:以下两个条件,任意各一个都会导致ANR

    • 当app处于前台时,在5s内无法相应用户输入或广播。
    • 当app没有activity处于前台时,广播接收器正在进行长时间的任务,且无法结束。

二. 检测和诊断ANR

  1. 使用Android vitals。Play Console会在vital dashboard中显示ANR数据。
    • 只要满足以下条件,Play Console会在vitals会认为 ANRs 过多:
      • 最少每天session的0.47%出现至少一次ANR
      • 最少每天session的0.24%出现2次或以上ANR
      • 每天session是指每天App的使用次数
  2. 诊断ANR,下面将列举一些典型的ANR原因
    • 在主线程进行很慢的I/O操作
    • 在主线程进行长时间的计算
    • 在主线程进行同步的binder远程调用,而远程调用需要很长的事件才能返回
    • 主线程阻塞,并等待其它线程执行长时间操作的同步块。
    • 通过自己进程或远程方法调用,主线程被其它线程死锁。此时,主线程并非在等待长时间操作完成,而是处于死锁状态。
  3. 通过以下方法来查找发生ANR的具体原因
    • 使用严格模式Strict mode,可以找到主线程中意外的I/O操作。可以在Application或Activity中使用。一般推荐在Application级就使用。
    • 在开发者模式中,选择打开”显示所有ANR对话框”。默认情况下这个开关是关闭的,所有有时后台app发生了ANR,但不会显示ANR对话框。
    • 使用Traceview,可以追踪app的运行轨迹,并确定主线程被卡住的地方。
    • 在root情况下,可以从设备拉取traces文件
      - 老版本中,文件位于 /data/anr/traces.txt
      - 较新的版本中,变成了多个文件 /data/anr/anr_*
    • 通过adb bugreport获取bug reports,或者从设备的开发者选项中获取。

三. 解决问题

  1. 耗时计算:确定主线程中超过5秒操作的可疑代码,尝试重现ANR。Traceview timeline 可以直观的显示很忙的主线程操作。

    • 确定长耗时的操作后,将这些操作以异步方式执行。可以使用AsyncTaskThread、线程池等方式来实现,保证主线程的交互响应。
    • Traceview同样可以显示工作线程正在处理的任务
    • 示例代码,修改前:

      @Override
      public void onClick(View view) {
          // 耗时很长的冒泡排序
          BubbleSort.sort(data);
      }
    • 示例代码,修改后:

      @Override
      public void onClick(View view) {
         // 将长耗时任务放在工作线程(子线程)中
         new AsyncTask<Integer[], Integer, Long>() {
             @Override
             protected Long doInBackground(Integer[]... params) {
                 BubbleSort.sort(params[0]);
             }
         }.execute(data);
      }
  2. IO操作:推荐将IO操作放到子线程中,就像上一条一样。

  3. 锁竞争:如果子线程对某资源加锁,而主线程同样需要这个资源,ANR就可能会发生。

    • 锁竞争不一定会导致ANR。
    • 由于锁竞争导致ANR发生时,主线程将处于阻塞状态。观察Traceview,可以发现主线的状态为MonitorWait
    • 查看trace file示例,也显示了资源锁竞争的问题:

      AsyncTask #2” prio=5 tid=18 Runnable
      | group=”main” sCount=0 dsCount=0 obj=0x12c333a0 self=0x94c87100
      | sysTid=25287 nice=10 cgrp=default sched=0/0 handle=0x94b80920
      | state=R schedstat=( 0 0 0 ) utm=757 stm=0 core=3 HZ=100
      | stack=0x94a7e000-0x94a80000 stackSize=1038KB > | held mutexes= “mutator lock”(shared held)
      at com.android.developer.anrsample.BubbleSort.sort(BubbleSort.java:8)
      at com.android.developer.anrsample.MainActivity$LockTask.doInBackground(MainActivity.java:147)

      locked <0x083105ee> (a java.lang.Boolean)
      at com.android.developer.anrsample.MainActivity LockTask.doInBackground(MainActivity.java:135)atandroid.os.AsyncTask L o c k T a s k . d o I n B a c k g r o u n d ( M a i n A c t i v i t y . j a v a : 135 ) a t a n d r o i d . o s . A s y n c T a s k 2.call(AsyncTask.java:305)
      at java.util.concurrent.FutureTask.run(FutureTask.java:237)
      at android.os.AsyncTask SerialExecutor S e r i a l E x e c u t o r 1.run(AsyncTask.java:243)
      at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
      at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
      at java.lang.Thread.run(Thread.java:761)

    • 示例代码,修改前:

      ```
      @Override
      public void onClick(View v) {
          // 子线程在lockedResource上持有锁
         new LockTask().execute(data);
      
         synchronized (lockedResource) {
             // 主线程在这里需求lockedResource
             // 但必须等待子线程结束...
         }
      }
      public class LockTask extends AsyncTask<Integer[], Integer, Long> {
         @Override
         protected Long doInBackground(Integer[]... params) {
             synchronized (lockedResource) {
                 // This is a long-running operation, which makes
                 // the lock last for a long time
                 BubbleSort.sort(params[0]);
             }
         }
      }
      ```
      
    • 示例代码,修改后:

      ```
      @Override
      public void onClick(View v) {
         WaitTask waitTask = new WaitTask();
         synchronized (waitTask) {
             try {
                 waitTask.execute(data);
                 // 等待子线程的通知
                 waitTask.wait();
             } catch (InterruptedException e) {}
         }
      }
      
      class WaitTask extends AsyncTask<Integer[], Integer, Long>{
         @Override
         protected Long doInBackground(Integer[]... params) {
             synchronized (this) {
                 BubbleSort.sort(params[0]);
                 // 完成,通知主线程
                 notify();
             }
         }
      }
      ```
      
    • 除了锁,还有其它的情况会导致主线程block。比如:
      • Semaphore 信号标
      • 资源池,比如数据库连接池
      • 其它互斥机制
    • 为了避免锁竞争导致的ANR,应当:
      • 仔细检查主线程中对锁的请求
      • 尽量缩短子线程持有锁的时间
      • 评估是否需要持有锁。如果真的需要,应当使用合理的线程通信方法。
  4. Deadlocks

    • 当两个线程互相等待对方持有的资源,导致都进入等待状态,就发生了死锁。
    • 如果UI线程和其它线程处于了死锁状态,很可能会发生ANR。
    • 请使用各种预防死锁算法来解决问题。
  5. 慢广播:如果花费太长的时间用来处理广播消息时,会出现ANR。

    • 以下两种情况会导致ANR:
      • onReceive() 执行时间太长。So不要在这个方法中执行耗时操作。
      • 广播接收器调用goAsync(),但没有调用PendingResult#finish()
    • 如果需要对广播消息进行更复杂的处理,应当推迟到IntentService中去执行。
    • 可以通过Traceview 来追踪耗时操作。
    • onReceive() 示例代码,修改前:

      @Override
      public void onReceive(Context context, Intent intent) {
          // 长耗时
          BubbleSort.sort(data);
      }
    • onReceive() 示例代码,修改后:

      @Override
      public void onReceive(Context context, Intent intent) {
          // The task now runs on a worker thread.
          Intent intentService = new Intent(context, MyIntentService.class);
          context.startService(intentService);
      }
      
      public class MyIntentService extends IntentService {
         @Override
         protected void onHandleIntent(@Nullable Intent intent) {
             BubbleSort.sort(data);
         }
      }
    • 广播接收器可以通过goAsync给系统发送一个请求更多时间来处理消息的信号,但在处理完成后,应该调用PendingResult#finish()

      final PendingResult pendingResult = goAsync();
      new AsyncTask<Integer[], Integer, Long>() {
         @Override
         protected Long doInBackground(Integer[]... params) {
             // This is a long-running operation
             BubbleSort.sort(params[0]);
             pendingResult.finish();
         }
      }.execute(data);

      请注意,只能在主线程中使用。如果将这段代码放到子线程中,ANR问题仍会出现。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值