ANR问题,相信是日常应用测试中,各位小伙伴都会遇到的问题。本篇对ANR的类型、原因及出现场景、以及ANR定位与分析思路进行了总结!
【一. ANR定义与分类】
ANR定义
ANR全称Application Not Responding,意思就是程序未响应。如果应用程序在UI线程被阻塞太长时间,就会出现ANR。出现ANR,通常系统会弹出一个提示提示框。
ANR类型
(1). 广播ANR
(2). ServiceANR
(3). ContentProviderANR
(4). InputANR
(5). 系统WatchDog
ANR超时阈值
不同组件的超时阈值不同,Service、Broadcast、ContentProvider 、Input的超时阈值如下
(1)BroadcastTimeout
前台Broadcast:onReceiver在10S内没有处理完成发生ANR。
后台Broadcast:onReceiver在60s内没有处理完成发生ANR。
(2)ServiceTimeout
前台Service:onCreate,onStart,onBind等生命周期在20s内没有处理完成发生ANR。
后台Service:onCreate,onStart,onBind等生命周期在200s内没有处理完成发生ANR (3)ContentProviderTimeout ContentProvider 在10S内没有处理完成发生ANR。
(4)KeyDispatchTimeout
input事件在5S内没有处理完成(如按键或者触摸)发生了ANR。
【二. ANR产生原因与出现场景】
1. 产生ANR原因
a. 耗时操作
b. 自身服务阻塞
c. 系统阻塞
d. 内存紧张
e. CPU资源抢占
2. 典型场景
a. 主线程频繁进行耗时的IO操作:如数据库读写
b. 多线程操作的死锁,主线程被block;
c. 主线程被Binder 对端block;
d. SystemServer中WatchDog出现ANR;
e. servicebinder的连接达到上限无法和和System Server通信
f. 系统资源已耗尽(管道、CPU、IO)
【三. ANR定位与分析】
1. ANR分析思路——traces
通常发生ANR时,首先去查找对应Trace(重要进程的各个线程调用栈trace信息)日志,看看主线程是否在处理该广播或被阻塞。
trace路径:/data/anr/traces.txt
trace导出:adb pull/data/anr/traces.txt
最新的ANR信息在最开始部分,我们从stacktrace中即可找到出问题的具体行数。
在文件中使用ctrl F 查找包名可以快速定位相关代码。特别注意:产生新的ANR,原来的 traces.txt 文件会被覆盖。
2. ANR其他分析思路与相关日志
如果发现堆栈完全处于空闲状态,那就需要结合log日志进行分析,包括logcat、kernel日志、cpuinfo以及meminfo等,参考顺序从前向后。
分析logcat思路
首先在日志中搜索(“anrin”,“low_memory”,“slow_operation”)等关键字,通过该类关键字主要是查看系统CPU负载,如果是发现应用进程CPU明显过高,那么很有可能是该进程抢占CPU过多导致,系统调度不及时,误认为应用发生了超时行为。
分析kernel思路
在此类日志中直接搜索lowmemorykiller,如果存在则查看发生时间和ANR时间是否大致对应,相差无几的话,可以从该日志中看到操作系统层面当前内存情况,Free Memory说明的是空闲物理内存,File Free说明的则是文件Cache,也就是应用或系统从硬盘读取文件,使用结束后,kernel并没有这正释放这类内存,加以缓存,目的是为了下次读写过程加快速度。当然,发现Free和Other整体数值都偏低时,Kernel会进行一定程度的内存交换,导致整个系统卡顿。同时这类现象也会体现在log日志“slow_operation”中,即系统进程的调度也会收到影响。
分析cpuinfo思路
这类日志,可以清晰的看到哪类进程CPU偏高,如果存在明显偏高进程,那么ANR和此进程抢占CPU有一定关系。当然,如发现Kswapd,emmc进程在top中,则说明遇到系统内存压力或文件IO开销。
分析meminfo思路
分析该类日志,主要是看哪类应用或系统占用内存偏高,如果应用内存占用比较正常,系统也没有发生过度内存使用,那么则说明系统中缓存了大量进程,并没有及时释放导致系统整体内存偏低。
3. traces.txt重要字段
main:main标识主线程,如果是线程,那么命名成“Thread-X”的格式,x表示线程id,逐步递增。
prio:线程优先级,默认是5
tid:tid不是线程的id,是线程唯一标识ID
group:是线程组名称
sCount:该线程被挂起的次数
dsCount:是线程被调试器挂起的次数
obj:对象地址
self:该线程Native的地址
sysTid:是线程号(主线程的线程号和进程号相同)
nice:是线程的调度优先级
sched:分别标志了线程的调度策略和优先级
cgrp:调度归属组
handle:线程处理函数的地址。
state:是调度状态
schedstat:从 /proc/[pid]/task/[tid]/schedstat读出,三个值分别表示线程在cpu上执行的时间、线程的等待时间和线程执行的时间片长度,不支持这项信息的三个值都是0;
utm:是线程用户态下使用的时间值(单位是jiffies)
stm:是内核态下的调度时间值
core:是最后执行这个线程的cpu核的序号。
找到java的堆栈信息定位代码位置,定位到问题。
【四. ANR分析案例】
分析案例一:Input ANR
分析案例二:在系统的方法上的锁没释放
WindowManagerGlobal.dumpGfxInfo
Blocked就一定有被持有的对象,这个有时候是发生在binder,就需要分析binder相关的log
分析案例三 内存问题
分析案例四 GC问题
观察Trace主线程堆栈,发现主线程在申请内存过程中被block,等待GC结束,
再看看其它线程状态,进一步查找发现,下面任务正在执行GC
Tid=8线程执行GC,导致主线程申请内存被Block,应用进程内存使用不当,导致GC时间过程,产生ANR。
【参考】
https://juejin.im/post/5be698d4e51d452acb74ea4c
https://www.jianshu.com/p/862ce91c1abf
https://droidyue.com/blog/2015/07/18/anr-in-android/
https://www.jianshu.com/p/388166988cef