一. ANR简介

  1. 描述:UI线程如果停止响应太长的时间, “Application Not Responding” (ANR) 就被触发。
  • 如果被阻塞的app处于前台,系统会显示一个ANR对话框。
  1. 触发:以下两个条件,任意各一个都会导致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的使用次数
  1. 诊断ANR,下面将列举一些典型的ANR原因
  • 在主线程进行很慢的I/O操作
  • 在主线程进行长时间的计算
  • 在主线程进行同步的binder远程调用,而远程调用需要很长的事件才能返回
  • 主线程阻塞,并等待其它线程执行长时间操作的同步块。
  • 通过自己进程或远程方法调用,主线程被其它线程死锁。此时,主线程并非在等待长时间操作完成,而是处于死锁状态。
  1. 通过以下方法来查找发生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);
}
  1. IO操作:推荐将IO操作放到子线程中,就像上一条一样。
  2. 锁竞争:如果子线程对某资源加锁,而主线程同样需要这个资源,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.MainActivityLockTask.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.AsyncTaskSerialExecutor 
        
         
         
           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,应当:
  • 仔细检查主线程中对锁的请求
  • 尽量缩短子线程持有锁的时间
  • 评估是否需要持有锁。如果真的需要,应当使用合理的线程通信方法。
  1. Deadlocks
  • 当两个线程互相等待对方持有的资源,导致都进入等待状态,就发生了死锁。
  • 如果UI线程和其它线程处于了死锁状态,很可能会发生ANR。
  • 请使用各种预防死锁算法来解决问题。
  1. 慢广播:如果花费太长的时间用来处理广播消息时,会出现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问题仍会出现。