一、首先,了解一下什么是ANR
ANR,是“Application Not Responding”的缩写,即“应用程序无响应”。系统会向用户显示一个对话框,用户可以选择“等待”而让程序继续运行,也可以选择“强制关闭”。
在Android中,应用程序的响应是由Activity Manager和WindowManager系统服务监视的 。当它监测到A、B、C情况中的一个时,Android就会针对特定的应用程序显示ANR:
A.在5秒内没有响应输入的事件(例如,按键按下,屏幕触摸)--主要类型
B.BroadcastReceiver在10秒内没有执行完毕
C.Service在特定时间内(20秒内)无法处理完成--小概率类型
造成ABC的原因有很多,比如在主线程中做了非常耗时的操作,如下载,io异常等。还需要注意的是产生这种ANR的前提是要有输入事件,如果用户没有触发任何输入事件,即便是主线程阻塞了,也不会产生ANR,因为InputDispatcher没有分发事件给应用程序,当然也不会检测处理超时和报告ANR了。
各个应用进程和系统进程的函数堆栈信息都输出到了/data/anr/traces.txt的文件中
二、ANR的一般分析思路
- 从手机的/data/traces/目录导出traces.txt文件;
- 从traces.txt文件获取ANR产生的时间点T1;
- 从traces.txt文件中获取main线程的运行状态、调用栈;
- 查看logcat日志,搜索“ANR in”,找出ANR产生的时间点T2,以及时间点T2前后几秒钟,在系统中运行的各进程CPU耗时占比;
- 在步骤4中找出目标进程的cpu耗时占比,确认是user占比高,还是kernel占比高,还是iowait占比高;
- 根据时间点T1、时间点T2,校准ANR产生的时间范围 [T1, T2] 或 [T2, T1];
- 查看logcat日志,将日志定位到步骤2)中获取的时间点 [T1, T2] 或 [T2, T1]附近;
- 查看logcat日志在时间点 [T1, T2] 或 [T2, T1]附近前后2分钟的日志,以还原ANR产生前后的场景;
- 根据以上步骤得出的信息,定位代码中可能的问题代码块;
- 解决问题,或提出阶段分析结论。
三、测试人员碰到ANR问题,应该怎么做
首先,应该截图,保留“证据”;
描述清楚出现步骤,并尝试复现,确认是否必现或出现概率
导出traces文件
获取traces文件命令:
adb pull /data/anr
- 导出logcat日志信息。
扩展一:traces文件信息解析
开头显示进程号、ANR发生的时间点和进程名称
----- pid 9183 at 2012-09-28 22:20:42 ----
Cmd line: com.example.anrdemo
DALVIK THREADS: //以下是各个线程的函数堆栈信息
//依次是:线程名、线程优先级、线程号、线程当前状态(TIMED_WAIT或SUSPENDED在anr时很常见)
//线程名称后面标识有daemon,说明这是个守护线程
"main" prio=5 tid=1 TIMED_WAIT
//依次是:线程组名称、suspendCount个数、debugSuspendCount个数、线程的Java对象地址、线程的Native对象地址
| group="main" sCount=1 dsCount=0 obj=0x4025b1b8 self=0xce68
//sysTid是线程号,主线程的线程号和进程号相同
| sysTid=9183 nice=0 sched=0/0 cgrp=default
handle=-1345002368| schedstat=( 140838632 210998525 213 )
at java.lang.VMThread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:1213)
.......
//Binder线程是进程的线程池中用来处理binder请求的线程
"Binder Thread #2" prio=5 tid=8 NATIVE
| group="main" sCount=1 dsCount=0 obj=0x40750b90 self=0x1440b8
| sysTid=9190 nice=0 sched=0/0 cgrp=default handle=1476256
| schedstat=( 915528 18463135 4 )
at dalvik.system.NativeStart.run(Native Method)
//JDWP线程是支持虚拟机调试的线程,不需要关心
"JDWP" daemon prio=5 tid=5 VMWAIT
| group="system" sCount=1 dsCount=0 obj=0x4074b878 self=0x16c958
| sysTid=9187 nice=0 sched=0/0 cgrp=default handle=1510224
| schedstat=( 366211 2807617 7 )
t dalvik.system.NativeStart.run(Native Method)
//“Signal Catcher”负责接收和处理kernel发送的各种信号,例如SIGNAL_QUIT、SIGNAL_USR1等就是被该线程
//接收到,这个文件的内容就是由该线程负责输出的,可以看到它的状态是RUNNABLE,不过此线程也不需要关心
"Signal Catcher" daemon prio=5 tid=4 RUNNABLE
| group="system" sCount=0 dsCount=0 obj=0x4074b7b8 self=0x150008
| sysTid=9186 nice=0 sched=0/0 cgrp=default handle=1501664
| schedstat=( 1708985 6286621 9 )
扩展二:如何避免ANR
绝对不要在主线程上进行复杂耗时的操作,比如说发送接收网络数据、进行大量计算、操作数据库、读写文件等,统统采用异步操作
broadCastReceiver 要进行复杂操作的的时候,可以在onReceive()方法中启动一个IntentService或者JobIntentService去做。
Service中的耗时操作最好也是采用异步任务
在设计及代码编写阶段避免出现出现同步/死锁、死循环等不恰当情况