ANR 是什么
ANR: Application Not Responding。
ANR 通常由以下三种情形产生:
- Key Dispatching Timeout:输入事件超过 5s 无响应,比如按键或触摸事件。
- Broadcast Timeout:比如前台广播在 10s (后台广播 60s)内无法完成
onReceive
的执行。 - Service Timeout:比如前台服务在 20s (后台服务 200s)内无法完成
onCreate
或onStartCommand
的执行。
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 来处理。