android ANR 分析

时间:2024-02-16 09:13:25

为什么会产生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含义说明:

binder_cpu

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,但是也有例外的,如下:

Android ANR的产生与分析

如果手机已经是完全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来实现。
  • 在设计及代码编写阶段避免出现出现同步/死锁或者错误处理不恰当等情况。