Android ANR日志分析
ANR,是“Application Not Responding”的缩写,即“应用程序无响应”。在Android中,ActivityManagerService(简称AMS)和WindowManagerService(简称WMS)会监测应用程序的响应时间,如果应用程序主线程(即UI线程)在超时时间内对输入事件没有处理完毕,或者对特定操作没有执行完毕,就会出现ANR。对于输入事件没有处理完毕产生的ANR,Android会显示一个对话框,提示用户当前应用程序没有响应,用户可以选择继续等待或者关闭这个应用程序(也就是杀掉这个应用程序的进程)。
以下四个条件可以产生ANR:
InputDispatching Timeout: 5s内无法响应屏幕触摸事件或键盘输入事件。
BroadcastQueue Timeout: BroadcastReceiver的事件(onRecieve方法)在规定时间内没处理完成(前台规定时间:10s,后台规定时间:60s)。
Service Timeout: 规定时间未完成启动 Timeout executing service(前台:20s 后台:200s)。
ContentProvider Timeout: ContentProvider的publish在10s内没进行完
ANR窗口的弹窗界面类位置:
aosp/frameworks/base/services/core/java/com/android/server/am/AppNotRespondingDialog.java
造成ANR的原因
应用在主线程上执行耗时的I/O的操作。
应用在主线程上进行长时间的计算。
主线程在对另一个进程进行同步 binder 调用,而后者需要很长时间才能返回。(如果我们知道调用远程方法需要很长时间,我们应该避免在主线程调用)
主线程处于阻塞状态,主动睡眠,或者等待其他线程上的同步方法或者同步代码块执行完毕。
主线程与其他线程之间发生死锁。
上述原因都会造成主线程被长时间占用,导致无法响应用户的操作,从而造成ANR。
注意:主线程做耗时操作本身是不会产生ANR的,导致ANR的根本还是应用程序无法在一定时间内响应用户的操作。因为主线程被耗时操作占用了,主线程无法对下一个操作进行响应才会ANR,没有需要响应的操作自然就不会产生ANR,或者应该这样说:主线程做耗时操作,非常容易引发ANR。
线程的状态是native则表示当前正在执行jni,
/data/anr/xxx
/data/tombstones/tombstone_X
/data/system/dropbox/
使用bugreport命令,需要在开发者选项里面启用提交错误报告
adb pull /data/anr/bugreport-traces.txt
tombstone,俗称“墓碑”
因为android系统是运行在Linux Kernel内核之上的,当内核出现异常,则内核异常机制会分辨出是什么原因,处理不了的直接panic。而对于运行在Linux Kernel内核之上的android系统,如果出现异常,一般会自动重启android层的,这就导致问题很难复现定位debug,则当android层出现异常,通常会将进程的上下文信息保存到tombstone(墓碑)中,
adb bugreport
ADLVIK THREADS (99) //当前进程总数99个线程
"main" prio=5 tid=1 Native //线程名main 主线程调用栈
2.1 ANR重现
这里使用生成一个按钮跳转到ANRTestActivity,在后者的onCreate()中主线程休眠20秒:
@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_anr_test);
// 这是Android提供线程休眠函数,与Thread.sleep()最大的区别是
// 该使用该函数不会抛出InterruptedException异常。
SystemClock.sleep(20 * 1000);
}
//ANR前后cpu的使用情况
//如果CPU使用量接近100%,说明当前设备很忙,有可能是CPU饥饿导致ANR
//如果CPU使用量很少,说明主线程被block了
//如果IOwait很高,说明主线程在进行I/O操作
----- pid 28207 at 2020-12-03 17:30:41 ----- Cmd line: com.example.produceanr //进程号、ANR发生时间和进程名称 DALVIK THREADS (14): 当前进程总数14个线程 //主线程调用栈 "main" prio=5 tid=1 Sleeping //线程名、线程优先级、线程号、线程当前状态 | group="main" sCount=1 dsCount=0 flags=1 obj=0x76e543c8 self=0x6ffcf70000 | sysTid=28207 nice=-10 cgrp=default sched=0/0 handle=0x6ffe4d7ed0 | state=S schedstat=( 1106323008 54303290 408 ) utm=97 stm=13 core=4 HZ=100 | stack=0x7fcbd76000-0x7fcbd78000 stackSize=8192KB | held mutexes= at java.lang.Thread.sleep(Native method) - sleeping on <0x0a2aeaaf> (a java.lang.Object) at java.lang.Thread.sleep(Thread.java:440) - locked <0x0a2aeaaf> (a java.lang.Object) at java.lang.Thread.sleep(Thread.java:356) at com.example.produceanr.MainActivity$1.onClick(MainActivity.java:23)
如出现ANR问题 ,可以Main log中可以搜索ANR in 关键字,
FATAL,leaked, block,Pid id XXX,crash,DALVIK THREADS, OOM,unfinished died
ANR问题:
出现ANR应当提供traces.txt文件,直接在文件中搜索 关键字。锁定三个方向:memoryleak(是否为低内存),CPU block(CPU使用率过高)、iowait(IO流使用过于频繁)
(1)memoryleak:首先根据Android log搜索低内存相关 low_memory 关键字,以确定是否存在低内存现象
(2)CPU block:搜索对应包出现ANR前后 TOTAL 关键字前的百分比,若百分比接近100% 说明CPU饥饿导致了ANR:
(3)iowait:搜索iowait 关键字查看出现ANR前的百分比,若百分比过高,说明I/O流使用过于频繁导致ANR,此项需修改相关数据库的加载流程,如下:
"main" prio=5 tid=1 Sleeping | group="main" sCount=1 dsCount=0 flags=1 obj=0x7682ab30 self=0x7bd3815c00 | sysTid=6317 nice=-10 cgrp=default sched=0/0 handle=0x7c59fc8548 | state=S schedstat=( 1009468742 32888019 224 ) utm=91 stm=9 core=4 HZ=100 | stack=0x7ff27e1000-0x7ff27e3000 stackSize=8MB | held mutexes=
线程名:main
线程优先级:prio=5 //prio -> java thread priority default is 5, (nice =0, linux thread priority 120), domain is [1,10]
线程ID: tid=1
线程状态:Sleeping //ZOMBIE, RUNNABLE, TIMED_WAIT, MONITOR, WAIT, INITALIZING,STARTING, NATIVE, VMWAIT, SUSPENDED,UNKNOWN
线程组名称:group="main"
线程被挂起的次数:sCount=1
线程被调试器挂起的次数:dsCount=0
线程的java的对象地址:obj= 0x7682ab30
线程本身的Native对象地址:self=0x7bd3815c00
线程调度信息:
Linux系统中内核线程ID: sysTid=6317与主线程的进程号相同
线程调度优先级:nice=-10
线程调度组:cgrp=default
线程调度策略和优先级:sched=0/0
线程处理函数地址:handle= 0x7c59fc8548
线程的上下文信息:
线程调度状态:state=S
线程在CPU中的执行时间、线程等待时间、线程执行的时间片长度:schedstat=(1009468742 32888019 224)
线程在用户态中的调度时间值:utm=91
线程在内核态中的调度时间值:stm=9
最后执行这个线程的CPU核序号:core=4
线程的堆栈信息:
堆栈地址和大小:stack=0x7ff27e1000-0x7ff27e3000 stackSize=8MB
“androidosnet ”daemon prio=5 tid=6 VMWAIT //如此,当线程名称后面标识是 daemon,说明这一个守护线程,可以不用管
“JDWP ”daemon prio=5 tid=6 VMWAIT //如此,JDWP线程是支持虚拟机调试的线程,不需要关心
"Signal Catcher" daemon prio=5 tid=4 RUNABLE //该线程是负责接收与处理kernel发送的各种信号,该线程不需要关心
评论