Android ANR简介

发布时间 2023-07-06 13:42:56作者: Hello-World3

一、ANR定义

ANR(Application Not Responding), 如果 Android 应用的界面线程处于阻塞状态的时间过长,会触发“应用无响应”(ANR) 错误。如果应用位于前台,系统会向用户显示一个对话框,如图 1 所示。ANR 对话框会为用户提供强行退出应用的选项。

当点击了Close app或者由于ANR引起了闪退之后,这时查看Logcat,一般可以发现ANR以及 /data/anr/trace.txt 等字样,可以知道出现ANR主要原因是我们在主线程做了太多耗时操作,这时你可以选择等待按钮,等待应用程序结束主线程的耗时操作,或者选择确定按钮,结束这个应用程序,ANR对于一个应用来说是不能承受之痛,其影响并不比应用发生Crash小。


二、ANR分类

出现ANR的一般有以下几种类型:

Input dispatching timed out:输入时间分发超过5s,包括按键和触屏事件。

Broadcast of Intent:前台广播需要在10s内完成,后台广播需要在60s内完成。

executing service:前台服务需要在20s内完成,后台则需要在200s内完成。

ContentProvider:几乎非常少见,publish执行未在10s内完成。

Context.startForegroundService() did not then call Service.startForeground():应用调用startForegroundService,然后5s内未调用startForeground出现ANR或者Crash,此问题属于应用未适配Android版本sdk。


三、产生ANR的原因

1. 诊断 ANR 时需要考虑以下几种常见模式:

(1) 应用在主线程上非常缓慢地执行涉及 I/O 的操作,如有复杂的layout布局、频繁的I/O操作。

(2) 应用在主线程上进行长时间的计算,如一些耗时操作。

(3) 主线程在对另一个进程进行同步 binder 调用,而后者需要很长时间才能返回。

(4) 主线程处于阻塞状态,为发生在另一个线程上的长操作等待同步的块。

(5) 主线程在进程中或通过 binder 调用与另一个线程之间发生死锁。主线程不只是在等待长操作执行完毕,而且处于死锁状态。


2. 发生ANR的进一步原因:

(1) 主线程存在耗时操作:主线程阻塞(Blocked)、挂起(suspend)、死锁、死循环、耗时操作等;

(2) cpu资源被抢占:其他进程某一时间点cpu占比高、某一刻系统的cpu占比过高,都会导致这一时间段无法抢到cpu时间片;

(3) 主线程卡在 binder 通信的对端:需要通过 binder info 查看对端信息;

(4) 系统或者应用自身可用内存紧张:系统一直在执行 lowmemory killer 操作查杀进程;

(5) 应用频繁crash:包括应用自身也容易导致前台应用出现anr的现象;

(6) 应用内存泄露;

(7) 系统原因导致:如冻结、温度过高、多媒体(音视频、编解码)、包管理、Block I/O、底层服务NE(native crash)、watchdog、内存黑洞、芯片能力等。


四、分析

1. 分析日志思路

(1) 首先在 android(logcat)日志中搜索“ANR in”关键字,通过此关键字主要查看

a. 在 logcat.txt 中查看发生ANR的应用进程、pid、类型;

b. cpu负载、内存压力、cpu使用情况等。

可以看到发生ANR的时间点、包名、原因(f89caeb是个代号不好看,在event log中可看)

07-06 06:03:52.995  1122  1262 I WindowManager: ANR in Window{f89caeb u0 com.xiaoli.gaodemap/com.xiaoli.gaodemap.AndroidMainActivity}. 
Reason:f89caeb com.xiaoli.gaodemap/com.xiaoli.gaodemap.AndroidMainActivity (server) is not responding. Waited 5000ms for MotionEvent(deviceId=-1,
eventTime=30462153000000, source=0x00001002, displayId=0, action=DOWN, actionButton=0x00000000, flags=0x00000000, metaState=0x00000000,
buttonState=0x00000000, classification=NONE, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, xCursorPosition=nan, yCursorPosition=nan,
pointers=[0: (141.0, 688.0)]), policyFlags=0x6b000000


(2) 其次查看 event (logcat -b events)日志

检索 am_anr 可以看到进入anr的App包名、时间、原因。

07-05 23:28:43.352  1122  7123 I am_anr  : [0,27646,com.xiaoli.gaodemap,949501511,Input dispatching timed out (e58f0f9 com.xiaoli.
gaodemap/com.xiaoli.gaodemap.AndroidMainActivity (server) is not responding. Waited 5000ms for FocusEvent(hasFocus=false))]


(3) 然后查看 /data/anr下的 trace日志

如:/data/anr/anr_2023-07-05-22-09-22-129

----- pid 3033 at 2023-07-05 22:09:22.145231750+0800 ----- //发生ANR的进程号和时间点
Cmd line: com.xiaoli.gaodemap  //发生ANR的进程包名
Build fingerprint: 'qti/sa8295/au8295:12/SQ3A.220705.003.A1/3763:userdebug/test-keys' //平台、版本类型
DALVIK THREADS (166): //当前进程共有166个线程
"main" prio=5 tid=1 Runnable //线程名"main",线程优先级,线程内部tid
  | group="main" sCount=0 ucsCount=0 flags=0 obj=0x70a5b778 self=0xb400007a506d4be0
  | sysTid=3033 nice=-10 cgrp=top-app sched=0/0 handle=0x7b9ce0d4f8
  | state=R schedstat=( 73482259066 4675464138 104538 ) utm=5345 stm=2002 core=7 HZ=100
  | stack=0x7ff8906000-0x7ff8908000 stackSize=8188KB
  | held mutexes= "mutator lock"(shared held)
//下面是线程的调用栈

上面列出的各字段的含义:

group: 分组,如 main、system.
sCount: 线程挂起次数。
obj: 当前线程关联的java线程对象
self: 当前线程地址
sysTid: 线程真正意义Linux下的TID。
nice: 调度优先级,默认120,此时是110.
cgrp: 线程所属的进程调度组
sched: 调度策略
handle: 函数处理地址
state: Linux线程状态
schedstat: CPU调度时间统计
utm: 用户态使用CPU时间
stm: 内核态使用CPU时间
core: 该线程最后运行的CPU核
HZ: 时钟频率,这里是10ms一次tick.
stack: 线程栈的地址空间
stackSize:栈的大小
held mutexes: 持有mutex锁的类似,有独占 exclusive 和共享 shared 两类。

Runnable 位置表示状态,有多种状态:Native(表示正在执行native方法)、Blocked、Suspended、Waiting、TimedWaiting、Runnable、Monitor、WaitingForTaskProcessor、Sleeping 等。


2. CPU使用率信息分析

从android(高通平台在android.txt,MTK平台在system*.txt)日志中,查看系统中各个进程的cpu使用率,首先关注的进程就是发生anr的进程、system_server、kswapd0、kworker和其他占比较高的进程、以及最终统计的整体cpu使用率信息。例如,如果kswapd0占比较高,就说明内存存在一定的压力;iowait很高就说明系统在一些I/O耗时操作,就可以结合上下文日志及系统日志辅助分析发生ANR的原因。如果很多进程的cpu使用率普遍较高,发生anr应用的cpu使用率较低,此时可以怀疑发生anr的应用拿不到cpu时间片导致anr等。


3. Memory角度分析

查看发生anr时间点前后的可用内存情况,以及系统查杀应用的频繁程度。


6. kernel日志分析思路

在日志中直接搜索关键字“lowmemorykiller”、“iowait”等,查看发生的时间点与发生anr的时间点是否基本对应。如果发生anr时间点附近,出现大量的lowmemorykiller日志信息,则说明当时内存已经严重紧张,可以较大概率认为是内存不足导致后台一直在查杀进程,同时影响前台应用的操作,导致前台应用操作耗时出现anr问题;如果出现iowait,则表示可能出现了I/O卡顿。


7. 综合系统功能进行整体分析

部分情况下,根据trace日志很难能够直接确定发生anr的原因,需要根据当时的系统运行情况进行辅助分析。综合当前的系统环境,可以从消息队列、系统可用内存、发热功耗、后台GC频率和时长、dex2oat耗时、冻结、频繁crash、温度过高、lowmemorykiller、root权限、system.err、system.out、binder_sample、slow operation 等角度在日志中搜索关键字进行分析。也存在这种情况,系统日志信息不足以分析出anr的问题,此时需要借助日志中的systrace日志进行详细分析,虽然说大部分时间都对不上,但是也存在对上的时候。

总之,ANR问题需要进行整体分析,结合系统中的所有关键信息共同得出发生ANR的准确结论。


五、典型实例

注,日志见:https://blog.csdn.net/feelabclihu/article/details/119962884

1. Block I/O

2. 卡GPU渲染

3. 应用适配sdk

4. 相机模块遇到的问题

5. 音频模块遇到的问题

6. 音频模块遇到的问题

7. 无焦点窗口

8. 内存泄漏

9. Binder耗尽

10. barrier阻塞

11. 消息过量

12. 内存不足

13. 应用死锁

14. MessageQueue

15. 自身NE导致

16. 权限拦截

17. 多媒体编码

18. provider问题

19. park锁耗时

20. cpu时间片不足

21. 空进程

22. 芯片性能不足

 

六、总结

1. 除了正常的接收处理超时外,也会有其他额外因素引发应用产生ANR,比如:
(1) 系统的kswapd0过高引发频繁的内存交换
(2) cpu占比过高导致无法获取足够的时间片
(3) binder资源耗尽无法及时通讯等,
这些都可以从系统日志中获取到。所以如果单凭trace日志无法分析出来的时候,我们要尽可能多的查看影响发生anr的因素,搜索关键字帮我们快速定位发生的anr问题;另外也可以通过查看日志中的systrace日志或者其他辅助信息来定位问题。

 

参考:

Android ANR问题总结_内核工匠:https://blog.csdn.net/feelabclihu/article/details/119962884
Android性能优化杂谈-如何监控和解决ANR问题?:https://blog.csdn.net/ljcITworld/article/details/104420422