为什么会产生ANR
在Android里,应用程序的响应是由ActivityManager和WindowManager服务系统服务监视的,当检测到下面三种情况的任何一种时,Android就会针对特定的应用程序显示ANR对话框。
- Activity的UI在5秒内没有响应输入事件(例如,按键按下,屏幕触摸)–主要类型
- BroadcastReceiver在10秒内没有执行完毕
- Service在特定时间内(20秒内)无法处理完成–小概率类型
造成ANR的原因有很多,无论是在Activity或者BroadcastReceiver还是在Service,我们看到都是在主线程中操作引起的ANR,因此我们应该避免在主线程做太多耗时的操作,网络请求不用说了,Android4.0以后就禁止在主线程成执行请求了,除此之外就是要注意如下几个方面:
- 主线程频繁进行IO操作,比如读写文件或者数据库;
- 硬件操作如进行调用照相机或者录音等操作;
- 多线程操作的死锁,导致主线程等待超时;
- 主线程操作调用join()方法、sleep()方法或者wait()方法;
- system server中发生WatchDog ANR;
- service binder的数量达到上限。
trace参数解读
"Binder_1" prio=5 tid=8 Native
| group="main" sCount=1 dsCount=0 obj=0x12c610a0 self=0x5573e5c750
| sysTid=12092 nice=0 cgrp=default sched=0/0 handle=0x7fa2743450
| state=S schedstat=( 796240075 863170759 3586 ) utm=50 stm=29 core=1 HZ=100
| stack=0x7fa2647000-0x7fa2649000 stackSize=1013KB
| held mutexes=
说明:
- 第0行:
- 线程名: Binder_1(如有daemon则代表守护线程)
- prio: 线程优先级
- tid: 线程内部id
- 线程状态: NATIVE
- 第1行:
- group: 线程所属的线程组
- sCount: 线程挂起次数
- dsCount: 用于调试的线程挂起次数
- obj: 当前线程关联的java线程对象
- self: 当前线程地址
- 第2行:
- sysTid:线程真正意义上的tid
- nice: 调度有优先级
- cgrp: 进程所属的进程调度组
- sched: 调度策略
- handle: 函数处理地址
- 第3行:
- state: 线程状态
- schedstat: CPU调度时间统计, 见
proc/[pid]/task/[tid]/schedstat
- utm/stm: 用户态/内核态的CPU时间(单位是jiffies), 见
proc/[pid]/task/[tid]/stat
- core: 该线程的最后运行所在核
- HZ: 时钟频率
- 第4行:
- stack:线程栈的地址区间
- stackSize:栈的大小
- 第5行:
- mutex: 所持有mutex类型,有独占锁exclusive和共享锁shared两类
schedstat含义说明:
nice值越小则优先级越高。此处nice=-2, 可见优先级还是比较高的;
schedstat括号中的3个数字依次是Running、Runable、Switch,紧接着的是utm和stm
- Running时间:CPU运行的时间,单位ns
- Runable时间:RQ队列的等待时间,单位ns
- Switch次数:CPU调度切换次数
- utm: 该线程在用户态所执行的时间,单位是jiffies,jiffies定义为sysconf(_SC_CLK_TCK),默认等于10ms
- stm: 该线程在内核态所执行的时间,单位是jiffies,默认等于10ms
可见,该线程Running=186667489018ns,也约等于186667ms。在CPU运行时间包括用户态(utm)和内核态(stm)。 utm + stm = (12112 + 6554) ×10 ms = 186666ms。
结论:utm + stm = schedstat第一个参数值。
线程有很多状态,了解这些状态的意义对分析ANR的原因是有帮助的.
traces.txt文件分析
如果拉取traces.txt日志文件
当产生ANR的时候系统会生成一个日志文件,日志存放在/data/anr/文件夹下面,一般名称为traces.txt,但是也有例外的,如下:
如果手机已经是完全root的了,可以直接通过DDMS的File Explorer直接导出来,如果不是root的手机,可以通过如下adb命令查看ANR日志文件位于哪里。
adb shell ls /data/anr/
然后通过adb的pull将日志文件拉取到指定的路径。
adb pull /data/anr/traces.txt d:/
但是如果手机没有进行root,执行adb pull命令就会出现如下提示:
remote object ‘/data/anr/traces.txt’ does not exist
这时候我们可以使用adb将文件copy一份到sdcard,然后再拉取出来。
adbshell
cat /data/anr/traces.txt >/mnt/sdcard/traces.txt
exit
一般traces.txt日志输出格式如下,本实例是在主线程中强行Sleep导致的ANR日志:
DALVIKTHREADS :
(mutexes: tll=0 tsl=0 tscl=0 ghl=0 hwl=0 hwll=0)
"main" prio=5 tid=1 Sleeping
| group="main" sCount=1 dsCount=0 obj=0x73f11000 self=0xf3c25800
| sysTid=2957 nice=0 cgrp=default sched=0/0 handle=0xf7770ea0
| state=S schedstat=( 107710942 40533261 131 ) utm=4 stm=6 core=2 HZ=100
| stack=0xff49d000-0xff49f000 stackSize=8MB
| heldmutexes=
atjava.lang.Thread.sleep!(Native method)
- sleepingon <0x31fd6f5d> (a java.lang.Object)
atjava.lang.Thread.sleep(Thread.java:1031)
- locked <0x31fd6f5d> (a java.lang.Object)
atjava.lang.Thread.sleep(Thread.java:985)
atcom.sunny.demo.MainActivity.startMethod(MainActivity.java:21)
atjava.lang.reflect.Method.invoke!(Native method)
atjava.lang.reflect.Method.invoke(Method.java:372)
atandroid.view.View$1.onClick(View.java:4015)
atandroid.view.View.performClick(View.java:4780)
atandroid.view.View$PerformClick.run(View.java:19866)
atandroid.os.Handler.handleCallback(Handler.java:739)
atandroid.os.Handler.dispatchMessage(Handler.java:95)
atandroid.os.Looper.loop(Looper.java:135)
atandroid.app.ActivityThread.main(ActivityThread.java:5254)
atjava.lang.reflect.Method.invoke!(Native method)
atjava.lang.reflect.Method.invoke(Method.java:372)
atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
通过traces.txt中 at com.zzx.demo.MainActivity.startMethod(MainActivity.java:21) 很容易就可以定位到我们的问题所在,MainActivity的第21行,然后我们可以看到代码:
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
这就是由于主线程睡眠10s导致的无法响应。开发中定位ANR问题日志有个很简单的规律,就是 直接找到我们自己开发App所使用的包名(包括第三方Library库)信息开始定位找就可以了 。
如何避免ANR
- 避免在主线程进行复杂耗时的操作,特别是文件读取或者数据库操作;
- 避免频繁实时更新UI;
- BroadCastReceiver 要进行复杂操作的的时候,可以在onReceive()方法中启动一个Service来处理;
- 避免在IntentReceiver里启动一个Activity,因为它会创建一个新的画面,并从当前用户正在运行的程序上抢夺焦点。如果你的应用程序在响应Intent广 播时需要向用户展示什么,你应该使用Notification Manager来实现。
- 在设计及代码编写阶段避免出现出现同步/死锁或者错误处理不恰当等情况。