ANR 问题分析

ANR 是什么

ANR: Application Not Responding。

ANR 通常由以下三种情形产生:

  • Key Dispatching Timeout:输入事件超过 5s 无响应,比如按键或触摸事件。
  • Broadcast Timeout:比如前台广播在 10s (后台广播 60s)内无法完成 onReceive 的执行。
  • Service Timeout:比如前台服务在 20s (后台服务 200s)内无法完成 onCreateonStartCommand 的执行。

ANR 产生原因

耗时操作

主线程做了一些耗时操作,比如 I/O,请求网络或进行了一些耗时的计算。

  • I/O 操作
    • 文件操作,比如 file.exists() 的判断,在某些低端机型上会引发 ANR。
    • 数据库操作
  • 网络操作,4.0后 Android 强制在子线程中进行。
  • 耗时计算,如对 bitmap 进行调整计算等。

线程饥饿

cpu 分配给了其他线程,而主线程一直处于阻塞状态。

线程死锁

当线程进入等待状态时,需要的资源被另一个线程持有,而该线程也在等待第一个线程所持有的资源。这种相互等待但又不肯释放会造成线程死锁。

如何分析 ANR

发生 ANR 时,系统会收集 ANR 相关的信息,直接打印在系统日志上,并且会把收集的 trace 信息保存在本地,因此分析的思路就有以下俩种。

logcat 日志

对于 logcat 日志我们可以根据 CPU 和 I/O 的使用率,可以大概分析出以下几点:

  • 如果 CPU 使用量接近100%,说明当前设备很忙,有可能是 CPU 饥饿了。
  • 如果 CPU 使用量很少,说明主线程被 BLOCK 了。
  • 如果 IOwait 很高,说明 ANR 有可能是主线程在进行 I/O 操作造成。

trace.txt

traces.txt 文件会记录应用的 ANR 问题,并且会打印当时 App 的运行堆栈信息。traces 文件夹可能有多份,会命名为:traces.txt, traces1.txt, traces2.txt 等,最新的ANR永远写在 traces.txt 文件中。我们可以把 trace.txt 文件拷贝出来:

1
adb shell cat /data/anr/traces.txt > d:/traces.txt

然后可以具体分析文件中的调用栈,定位到具体代码,结合 ANR 问题的类型,解决代码的逻辑缺陷。

怎么避免 ANR

Strict Mode

开启 Strict Mode,检测在主线程中执行的磁盘或网络操作。我们可以把这些操作移到子线程中执行,同时可以在主线程中使用 dialog 来保持在主线程中的响应。

耗时操作

  • 将耗时操作都放到其他非 UI 线程、AsyncTask 或 IntentService 中执行。
  • 当存储简单信息时,可以考虑使用文件或者 sharedpreference 代替数据库。
  • 如果使用数据库,考虑使用 batch 方法或事务提高效率。

线程操作

  • 主线程中尽量避免调用任何 synchronized 方法。
  • 主线程中尽量避免调用 wait(), sleep() 操作。

使用 HandlerThread

普通 Handler 的 handleMessage 是在主线程中执行,当需要在接收到消息后执行耗时操作时,可以用 HandlerThread 替换 Thread。HandlerThread 比 Thread 多了一个 Looper,而Looper是线程相关的,所以 Looper 分发给 Handler 的消息也会在 HandlerThread 创建的线程里,这样就实现了子线程之间的通信。

HandlerThread 有自己的消息队列,处理任务时是串行的,不太适合频繁且耗时的网络请求,容易阻塞。

使用 IntentService

IntentService 继承 Service,内部封装了 HandlerThread 和 Handler,轮流把每个收到的 Intent 放到子线程中执行,完成所有工作后自动退出,这样就不会阻塞主线程。

减少 BroadCast receivers 的操作

broadCastReceiver 要进行复杂操作的的时候,可以在 onReceive() 方法中启动一个 IntentService 来处理。