ANR

623 阅读4分钟

Anr简介

ANR 是负责更新界面的应用主线程无法处理用户输入事件或绘制操作。

诊断Anr

常见的几种形式:

  1. 应用在主线程上非常缓慢地执行涉及 I/O 的操作。
  2. 应用在主线程上进行长时间的计算。CPU超负荷。
  3. 主线程在对另一个进程进行同步 binder 调用,而后者需要很长时间才能返回。
  4. 主线程处于阻塞状态,为发生在另一个线程上的长操作等待同步的块。
  5. 主线程在进程中或通过 binder 调用与另一个线程之间发生死锁。主线程不只是在等待长操作执行完毕,而且处于死锁状态。
  6. 内存不够,频繁GC。

解决Anr

主线程上执行速度缓慢的代码

在您的代码中找出应用的主线程忙碌时间超过 5 秒的位置。在您的应用中查找可疑用例并尝试重现 ANR。

image.png

主线程上的 IO

在主线程上执行 IO 操作是导致主线程上操作速度缓慢的常见原因,主线程上操作速度缓慢会导致 ANR。建议将所有 IO 操作移至工作线程。

锁争用

在某些情况下,导致 ANR 的工作并不是直接在应用的主线程上执行。如果某工作线程持有对某项资源的锁,而该资源是主线程完成其工作所必需的,这种情况下就可能会发生 ANR。

image.png

但如果用户仍然会遇到 ANR,您应该在 Android Device Monitor 中查看主线程的状态。通常情况下,如果主线程已准备好更新界面并且总体上响应速度较快,则处于 RUNNABLE 状态。

但如果主线程无法继续执行,则它处于 BLOCKED 状态,并且无法响应事件。该状态在 Android Device Monitor 中会显示为“Monitor”或“Wait”

image.png

分析Anr

查看cpu使用

CPU usage from 653ms to -6471ms ago:
xxxx
xxxx
xxxx
0% TOTAL: 0% user + 0% kernel + 0% iowait + 0% softirq
CPU usage from 5922ms to 6446ms later:
xxxx
xxxx
xxxx
0% TOTAL: 0% user + 0% kernel + 0% iowait

最重要的是TOTAL这一行,如果是100%,说明cpu占用满了,有可能造成ANR。

查看内存使用

Heap: 2% free, 116MB/119MB; 1245050 objects
Dumping cumulative Gc timings
xxxx
xxxx
Total time spent in GC: 21.510s
Mean GC size throughput: 60MB/s
Mean GC object throughput: 927110 objects/s
Total number of allocations 21187210
Total bytes allocated 1GB
Free memory 3MB
Free memory until GC 3MB
Free memory until OOME 395MB
Total memory 119MB
Max memory 512MB
Total mutator paused time: 906.554ms
Total time waiting for GC to complete: 995.131us

上面内存不足了。

  • Total number of allocations 476778  进程创建到现在一共创建了多少对象
  • Total bytes allocated 52MB 进程创建到现在一共申请了多少内存
  • Total bytes freed 52MB   进程创建到现在一共释放了多少内存
  • Free memory 777KB    不扩展堆的情况下可用的内存
  • Free memory until GC 777KB  GC前的可用内存
  • Free memory until OOME 383MB  OOM之前的可用内存
  • Total memory 当前总内存(已用+可用)
  • Max memory 384MB 进程最多能申请的内存

查看出现ANR的堆栈信息

出现ANR,一定是发生的main线程,所以我们只需要看main线程就可以了.

DALVIK THREADS (298):
"main" prio=5 tid=1 Suspended
| group="main" sCount=1 dsCount=0 obj=0x744dd9b0 self=0xab567160
| sysTid=10338 nice=-6 cgrp=default sched=0/0 handle=0xf720dbec
| state=S schedstat=( 130165872096 18484328377 132962 ) utm=11729 stm=1287 core=5 HZ=100
| stack=0xff2f7000-0xff2f9000 stackSize=8MB
| held mutexes=
at android.os.MessageQueue.enqueueMessage(MessageQueue.java:353)
- locked <0x33ef2fb9> (a android.os.MessageQueue)
at android.os.Handler.enqueueMessage(Handler.java:631)
at android.os.Handler.sendMessageAtTime(Handler.java:600)
at android.os.Handler.sendMessageDelayed(Handler.java:570)
at android.os.Handler.sendMessage(Handler.java:507)
at com.tencent.reading.system.l.ʻ(NetTipsReceiver.java:97)
at com.tencent.reading.system.k.run(NetStatusReceiver.java:340)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5400)
at java.lang.reflect.Method.invoke!(Native method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1037)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:832)

堆栈日志最重要有两个方面,一个是当前线程的状态,一个是函数的调用关系链

上述日志中的线程的状态是Suspended,基本可以判断是内存的问题

- THREAD_UNDEFINED = -1, / makes enum compatible with int32_t /
- THREAD_ZOMBIE = 0, / TERMINATED /
- THREAD_RUNNING = 1, / RUNNABLE or running now /
- THREAD_TIMED_WAIT = 2, / TIMED_WAITING in Object.wait() /
- THREAD_MONITOR = 3, / BLOCKED on a monitor /
- THREAD_WAIT = 4, / WAITING in Object.wait() /
- THREAD_INITIALIZING= 5, / allocated, not yet running /
- THREAD_STARTING = 6, / started, not yet on thread list /
- THREAD_NATIVE = 7, / off in a JNI native method /
- THREAD_VMWAIT = 8, / waiting on a VM resource /
- THREAD_SUSPENDED = 9, / suspended, usually by GC or debugger /

查看Activity堆栈的信息

看是否发生内存泄露。因为GC的Anr。

Native层

"main" prio=5 tid=1 Native #00 pc 00000000000c3d88 
/apex/com.android.runtime/lib64/bionic/libc.so (__epoll_pwait+8) 
#00 pc 0000000000018120 /system/lib64/libutils.so 
(android::Looper::pollInner(int)+144) #00 pc 0000000000017ff0 
/system/lib64/libutils.so (android::Looper::pollOnce(int, int*, int*, void**)+56) #00 pc 000000000013f270 
/system/lib64/libandroid_runtime.so 
(android::android_os_MessageQueue_nativePollOnce(_JNIEnv*, 
_jobject*, long, int)+44) at 
android.os.MessageQueue.nativePollOnce (MessageQueue.java) at 
android.os.MessageQueue.next (MessageQueue.java:336) at android.os.Looper.loop (Looper.java:184) at android.app.ActivityThread.main (ActivityThread.java:7835) at 
java.lang.reflect.Method.invoke (Method.java) at 
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run 
(RuntimeInit.java:492) at 
com.android.internal.os.ZygoteInit.main (ZygoteInit.java:984)

上面这个就比较难定位了,信息比较少,可以使用监测框架,提供更多的信息。

总结:

为了避免Anr,代码尽量注意一下几点:

  1. IO
  2. GC
  3. GPU满了
  4. 线程锁

参考: