ANR(Application Not Responding,即应用程序无响应)。在Android中,当应用程序在规定时间内没有处理完毕相应的事件,系统就会报出ANR。
ANR类型
产生原因
PS:一定要把ANR和JE区分开,ANR是XXX无响应,JE是XXX已停止
ANR类的问题有些可以直接从日志中排查出原因,例如广播类ANR;有些可能因为系统不稳定(例如系统进程system server进程崩溃,CPU耗时,IO耗时,低内存等原因)导致很难定位。因此我们需要一套思路来分析定位ANR。
无论是MTK平台还是展锐平台,我们都需要如下关键信息的日志:
logcat属于android原生提供的抓取日志的方式。可以通过logcat来初步定位anr发声时间以及上下文系统或者各个进程状态。
MTK平台的AP日志中主要有main_log和sys_log,如果跟通信相关,还需要去查看radio_log。
展锐平台的AP日志中主要有android.log、android_main.log、android_system.log、android_radio.log。
envents也属于android原生提供的一种日志。通过这个日志可以很简洁的呈现出系统的事件处理流程,包括时间,状态,便于我们分析定位问题
MTK平台的AP日志中主要是events_log开头的文件;展锐平台AP日志中主要android_events.log。
【Android日志分析】EventLog_android event日志_xqliu2134的博客-CSDN博客
MTK通过db的方式来记录系统发生的一切JE/NE/ANR相关的堆栈,如下图,但是需要通过mtk的专用工具才能解析。
展锐平台的AP日志中有专门traces文件夹,该文件夹存放了每一次ANR的堆栈调用,系统信息,文件格式为xx_xxx-xx_anr_xx_xx_xx。
MTK和展锐官网都有详细说明如何分析ANR问题,但这里我更推荐展锐的分析思路。
展锐
相比两种分析思路,大体流程类似,可以总结分为如下几个步骤:
如果测试提供的日志有AEE目录,那么可以先检查db_history快速找到对应的db文件:
使用MTK的GAT工具加载db文件并解析:
直接从YLOG的AP日志中找到对应时间端的traces文件:
如果DEBUG或者YLOG里面没有相关的ANR日志,那么只有从logcat中查看ActivityManager进程是否有相关ANR的打印(PS:YLOG通常在android_system.log),例如:
还可以再envent_log日志中搜索am_anr类型的日志,例如:
E012594 06-14 13:54:40.652 1110 4566 I am_anr : [0,1648,com.android.settings,684310085,Input dispatching timed out (105fb5 com.android.settings/com.android.settings.network.telephony.MobileNetworkActivity (server) is not responding. Waited 8007ms for MotionEvent)]
为什么要优先去定位主线程信息?
根据ANR产生原因来看,即应用主线程做了耗时操作,根据分类可以是广播接受者、内容提供者、服务(四大组件都运行在主线程)中做了耗时操作;
当然Acitcity也有可能存在耗时产生InputDispatchingTimedOut类型的ANR,例如activity没有即使显示出来(分析焦点),activity view没有及时响应触摸点击等事件,handler中消息处理超时 等。
MTK通过db文件中的__exp_main.txt文件来定位,展锐通过traces来定位。traces显示的有如下几部分:
Signal Catcher线程后文会详细介绍,这里只需要知道他是抓取Java虚拟机堆栈的线程
线程堆栈中,如下截图表明为主线程,关键特征:线程组为main,tid为1
线程名: main(如有daemon则代表守护线程)
prio: 线程优先级
tid: 线程内部id
线程状态: NATIVE
"main" prio=5 tid=1 Native
| group="main" sCount=1 ucsCount=0 flags=1 obj=0x735086e8 self=0xb4000071e8c707b0
| sysTid=1329 nice=0 cgrp=default sched=1073741824/0 handle=0x732dad44f8
| state=S schedstat=( 136542970431 292470333243 553823 ) utm=8774 stm=4879 core=3 HZ=100
| stack=0x7febede000-0x7febee0000 stackSize=8188KB
| held mutexes=
第1行group: 线程所属的线程组
第1行sCount: 线程挂起次数
第1行dsCount: 用于调试的线程挂起次数
第1行obj: 当前线程关联的java线程对象
第1行self: 当前线程地址
第2行sysTid:线程真正意义上的tid
第2行nice: 调度有优先级
第2行cgrp: 进程所属的进程调度组
第2行sched: 调度策略
第2行handle: 函数处理地址
第3行state: 线程状态
第3行schedstat: CPU调度时间统计, 见proc/[pid]/task/[tid]/schedstat
第3行utm/stm: 用户态/内核态的CPU时间(单位是jiffies), 见proc/[pid]/task/[tid]/stat
第3行core: 该线程的最后运行所在核
第3行HZ: 时钟频率
第4行stack:线程栈的地址区间
第4行stackSize:栈的大小
第5行mutex: 所持有mutex类型,有独占锁exclusive和共享锁shared两类
schedstat含义说明:
nice值越小则优先级越高。此处nice=0, 可见优先级还是比较高的;
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
在分析各个线程的时候,首先还需要关注的是他们线程名称和线程状态:
线程名"main":通常为主线程,这个跟group不一样,有些子线程的group也是main,但他们不是主线程
线程名"Binder":通常为binder子线程
线程状态Native:表明该线程当前运行在native层,通常会有一些c/c++代码堆栈打印
线程状态Blocked:表明该线程被阻塞,通常为java某个方法被卡住,或者等待某个锁的释放
Android Runtime | Trace文件的生成机制_waitholdinglocks_小陈乱敲代码的博客-CSDN博客