一 ANR简介
ANR和crash的区别:ANR不一定是由于程序的异常错误导致的,一般是应用处理长时间没有响应导致主线程不能处理下一件事情
ANR(Application not responding)即应用无响应,应用发生anr的几种类型
1 KeyDispatchTimeout:最常见的anr类型是对输入事件5s内无响应,比如按键或触摸事件在此时间内无响应
2 BroadcastTime:BroadcastReceiver在指定事件(原生系统默认是10s)内无法处理完成,并且没有结束执行onReceive
3 ServiceTimeout:这种类型在Android应用中出现的概率很小,是指service在特定时间(原生系统是20s)内无法处理完成
引起anr的根本原因总体来说有以下两种:
1)应用程序自身逻辑有缺陷,或者在某些异常场景触发了此缺陷,如主线程堵塞、死循环等导致
2)由于Android设备其他进程的CPU占用高,导致当前应用进程无法抢占到CPU时间片
二 ANR分析
在Android系统上,如果发生ANR,Logcat会产生对于的日志和一个trace文件,主要是分析这两个信息
导出命令:adb pull /data/anr/traces.txt D:\temp
1)Logcat分析
关键信息如下:
ANR IN:发生anr的具体类
PID:发生ANR的进程,系统在此时会生成trace文件,当前的时间点也是发生anr的具体时间,以及生成trace文件的时间
Reson:当前anr的类型以及导致anr的原因
CPU usage:CPU的使用情况,在日志CPU usage有两个时间点,第一个是发生anr前的CPU使用情况,第二个是发生anr后的CPU使用情况
从logcat中除了能看出在哪个类发生anr以及anr的类型,具体的原因主要还是要看CPU的使用情况,如果CPU使用量很少,说明主线程可能阻塞,如果IOwait很高,说明anr有可能是由于主线程进行耗时的I/O操作造成的
2)trace分析
trace文件保存在/data/anr/trace.txt
这个日志很容易定位是onClick事件中超时导致
场景二:
在Activity的onCreate中开启了一个线程,在线程中执行testANR(),而testANR()和initView()都被加了同一个锁,为了百分之百让testANR()先获得锁,特意在执行initView()之前让主线程休眠了10ms,这样一来initView()肯定会因为等待testANR()所持有的锁而被同步住,这样就产生了一个稍微复杂些的ANR。这个ANR是很参考意义的,这样的代码很容易在实际开发中出现,尤其是当调用关系比较复杂时,这个时候分析ANR日志就显得异常重要了。下面的代码中虽然已经将耗时操作放在线程中了,按道理就不会出现ANR了,但是仍然要注意子线程和主线程抢占同步锁的情况
首先看主线程,如下所示。可以看得出主线程在initView方法中正在等待一个锁<0x422a0120>,这个锁的类型是一个MainActivity对象,并且这个锁已经被线程id为11(即tid=11)的线程持有了,因此需要再看一下线程11的情况。
tid是11的线程就是“Thread-13248”,就是它持有了主线程所需的锁,可以看出“Thread-13248”正在sleep, sleep的原因是MainActivity的57行,即testANR方法。这个时候可以发现testANR方法和主线程的initView方法都加了synchronized关键字,表明它们在竞争同一个锁,即当前Activity的对象锁,这样一来ANR的原因就明确了,接着就可以修改代码了
Android studio分析trace的工具:Analyze Stacktrace
Analyze Stacktrace使用方法
1 在Android studio工具栏中,选择Analyze ->Analyze Stacktrace,打开Analyze Stacktrace工具窗口
2 将trace.txt中的内容复制到窗口,单击Normalize按钮,生成Thread Dump列表
3 如果某个线程被标红,说明此线程被堵塞了,然后在右边的详细信息中查看堵塞的具体原因
注:部分anr是由于多个线程之前的同步锁导致,在使用Analyze Stacktrace分析时,可以再详情页面查看是在等待哪个线程,再定位到对应的线程