原文地址: https://www.jianshu.com/p/9db73a26a8bd
https://www.jianshu.com/p/d19c34e7e9bd
返回知识列表:Android知识点list
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了。
ANR发生时都会在log中输出错误信息,从log中可以获得ANR的类型,CPU的使用情况,CPU使用率过高有可能是CPU饥饿导致了ANR。CPU使用率过低说明主线程被block了,如果IOwait高是因为主线程进行I/O操作造成的。
除了log输出外,你会发现各个应用进程和系统进程的函数堆栈信息都输出到了一个/data/anr/traces.txt的文件中,这个文件是分析ANR原因的关键文件.要获取到该文件可使用adb指令进行赋权后拉出查看调用stack。通过log、trace.text、代码结合分析ANR的成因(iowait?Memoryleak?Block?)
要避免问题的产生,就要抓住问题产生的原因(ABC三种):
(1)避免在主线程上进行复杂耗时的操作,比如说发送接收网络数据/进行大量计算/操作数据库/读写文件等。这个可以通过使用AsyncTask或者使用多线程来实现。
(2)broadCastReceiver 要进行复杂操作的的时候,可以在onReceive()方法中启动一个Service来处理
(3)在设计及代码编写阶段避免出现出现同步/死锁或者错误处理不恰当等情况。
该文章只是简单概括介绍ANR,很多ANR问题trace文件是分析不了的,例如我们的系统上,人脸识别活体攻击的时候,native算法耗尽cpu资源导致其他app无法抢占cpu时间片导致anr,假如ANR的app是你开发的,估计查到死也找不到问题所在,类似这类问题也写过简要的分析文章:
各个应用进程和系统进程的函数堆栈信息都输出到了/data/anr/traces.txt的文件中,(注意是 当前的,所有的)
获取traces 文件:adb pull /data/anr
pull目录的原因是有些手机会对不同的应用输出专属的traces文件,所以/data/anr目录下会有一堆traces文件。一般三方app开发只关心自己的应用,而且没有root权限。所以只能分析traces文件定位anr,根据问题复现的模块、页面再结合traces文件三大常见的anr问题都能解决。打开traces文件搜索自己的应用包名就能在前后的位置上找到引发anr的类和方法。如下:
1.主线程被阻塞,输入事件5s没有响应,如onClick事件。这是anr问题的主要类型,一般开发者不会犯这样的错,凡是耗时的操作都另开线程处理,如果疏忽了看看自己的代码就知道怎么处理
2.BroadcastReceiver是在程序主线程运行,而且默认情况下BroadcastReceiver的运行时间为10s,所以不能有耗时操作,如果耗时超过10s就会导致anr,从traces文件log就可以看出onReceive不能进行耗时任务。
3.Service是计算型组件,虽然在后台运行,但是本质上它也跑在主线程,如果你的服务要做任何CPU密集型(如MP3播放)或阻塞(如网络)操作,都要放在子线程中,否则耗时超过20s就会导致anr。注意anr方法是我自己写的方法。onStartCommand()方法中有耗时任务将其放到子线程中去就可以了,例如:将下面的anr()方法放到子线程
//开头显示进程号、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三种情况前文已经详细呈现,避免anr思路已经清晰,简单总结下:
2.broadCastReceiver 要进行复杂操作的的时候,可以在onReceive()方法中启动一个IntentService或者JobIntentService去做。
4.在设计及代码编写阶段避免出现出现同步/死锁、死循环等不恰当情况
对系统开发者来说只有/data/anr/traces.txt文件是远远不够的。因为虽然anr了但是traces文件没有anr的log或者traces文件有log,但是并不能定位到导致anr问题的原因。
ANR对系统的稳定性影响是比较大的,测试中心会将ANR作为系统稳定性的重要case。系统开发者一般就要依赖工具和系统log来分析,anr报在你负责的模块,但是导致anr的原因很可能在别的模块,尤其在跑稳定性测试的时候一些个别ANR问题很难调查清楚,毕竟脚本跟手动操作还是有些区别,而且像内存碎片,硬件故障等等的问题基本应用层是无从查起的,至少依靠个人是很难找出来,可能需要个团队一起关注(比如内核,BSP),甚至是反馈给厂商去解决。
根据我的经验以下几种情况都会导致ANR:
(个人观点,以上才是干货,平台工程师必须掌握的能力。在后续一一补上这些内容)
当然前面app中的ANR问题也会出现,系统开发比较庞杂,也没法整理出通用的情况案例,只能说ANR之后去如何分析。
我个人是MTK平台做的比较多,平台都有自己的工具,也会提供文档,看看文档摸索一下基本就能干活了
GAT是MTK在DDMS基础上进行二次开发封装的一个集多种debug功能为一体的工具,除了包含原有DDMS的功能以外还支持kernel抓取,获取native进程列表,backtrace decode,提取手机端debug信息等功能。当MTKLogger出现异常时,经常会需要使用GAT工具来抓取log。
mtklog里面都有意个aee_exp文件,里面有两个文件一个是ZZ_INTERNAL,只有一句话,说出报的是什么错误,发生错误的进程号,发生错误的程序,报错时间等信息。
另外一个文件叫db.01.JE,这个文件里面放的报错的具体信息以及报错时候机器各种状态的保存,但是这个文件只能用mtk特制的gat软件打开。
点击菜单上window--openlogView,点击file-OpenAeeDB,选择要打开的dbg格式的文件,左边的一列目录是各种报错时候的信息,最主要的是第一项exp_main.txt,在同级目录下会输出所有文件。
点开能发现最主要的报错信息,信息描述的很直接,对应自己的程序就明白这些信息
Build Info: //编译信息
Flavor Info: 'None'
Exception Log Time: //anr记录时间
Exception Class: ANR
Exception Type: system_app_anr
Current Executing Process:
com.android.settings
com.android.settings v19 (4.4.4-eng.scm.1428072972)
Backtrace:
Process: com.android.settings
Flags: 0x40c8be45
Package: com.android.settings v19 (4.4.4-eng.scm.1428072972)
Subject: Broadcast of Intent { act=[android.net] (http://android.net/).conn.CONNECTIVITY_CHANGE flg=0x4000010 cmp=com.android.settings/.ales.NetworkReceiver (has extras) }
//如果是在某个页面的话,会将页面类名也输出
Build: alps**************/1428069651:user/test-keys
Android time :[2017-12-14 20:07:52.45] [5284.377]
CPU usage from 3399ms to 151ms ago:
100% 2281/com.***.app: 90% user + 10% kernel / faults: 20828 minor 4 major
22% 1717/system_server: 13% user + 9.1% kernel / faults: 821 minor 7 major
21% 192/logd: 3.6% user + 17% kernel / faults: 6 minor
20% 237/mediaserver: 7.3% user + 13% kernel / faults: 1301 minor
8.2% 243/mobile_log_d: 2.4% user + 5.7% kernel / faults: 66 minor
3.3% 2442/tx_thread: 0% user + 3.3% kernel
3.3% 6208/logcat: 0.3% user + 3% kernel
2.7% 212/surfaceflinger: 0.3% user + 2.4% kernel
2.4% 2138/com.android.systemui: 1.8% user + 0.6% kernel / faults: 40 minor
1.2% 2128/sdcard: 0% user + 1.2% kernel
0.9% 7/rcu_preempt: 0% user + 0.9% kernel
............
56% TOTAL: 37% user + 18% kernel + 0% iowait + 0.6% softirq
Android time :[2017-12-14 20:08:14.42] [5306.347]
CPU usage from 35171ms to 0ms ago:
76% 2281/com.****.app: 61% user + 14% kernel / faults: 120156 minor
52% 237/mediaserver: 21% user + 31% kernel / faults: 13346 minor
43% 192/logd: 6.8% user + 36% kernel / faults: 116 minor
20% 1717/system_server: 12% user + 7.6% kernel / faults: 1734 minor 6 major
18% 212/surfaceflinger: 3.3% user + 14% kernel / faults: 30 minor
16% 243/mobile_log_d: 5.7% user + 10% kernel / faults: 1236 minor
7.5% 6208/logcat: 0.7% user + 6.7% kernel
2.4% 2138/com.android.systemui: 1.5% user + 0.9% kernel / faults: 307 minor
2.2% 2442/tx_thread: 0% user + 2.2% kernel
......
70% TOTAL: 33% user + 36% kernel + 0% iowait + 0.3% softirq
ANR发生之前和之后一段时间的CPU使用率
CPU使用率可以理解为一段时间(记作T)内除CPU空闲时间(记作I)之外的时间与这段时间T的比值,用公式表示可以写为:
CPU使用率= (T – I) / T
而时间段T是两个采样时间点的时间差值。之所以可以这样计算,是因为Linux内核会把从系统启动开始到当前时刻CPU活动的所有时间信息都记录下来,我们可以通过查看“/proc/stat”文件获取这些信息。主要包括以下几种时间,这些时间都是从系统启动开始计算的,单位都是0.01秒:
user: CPU在用户态的运行时间,不包括nice值为负数的进程运行的时间
nice: CPU在用户态并且nice值为负数的进程运行的时间
system:CPU在内核态运行的时间
idle: CPU空闲时间,不包括iowait时间
iowait: CPU等待I/O操作的时间
irq: CPU硬中断的时间
softirq:CPU软中断的时间
进程的CPU使用率最后输出的“faults: xxx minor/major”部分表示的是页错误次数,当次数为0时不显示。major是指Major Page Fault(主要页错误,简称MPF),内核在读取数据时会先后查找CPU的高速缓存和物理内存,如果找不到会发出一个MPF信息,请求将数据加载到内存。Minor是指Minor Page Fault(次要页错误,简称MnPF),磁盘数据被加载到内存后,内核再次读取时,会发出一个MnPF信息。一个文件第一次被读写时会有很多的MPF,被缓存到内存后再次访问MPF就会很少,MnPF反而变多,这是内核为减少效率低下的磁盘I/O操作采用的缓存技术的结果。
如果ANR发生时发现CPU使用率中iowait占比很高,可以通过查看进程的major次数来推断是哪个进程在进行磁盘I/O操作。
LOG怎么分析
首先了解一下主要的log文件
__exp_main.txt:异常类型,调用栈等关键信息。
SYS_ANDROID_LOG:android main log
SYS_KERNEL_LOG:kernel log
SYS_VERSION_INFO:kernel版本,用于和vmlinux对比,只有匹 配的vmlinux才能用于分析这个异常。
SYS_ANDROID_EVENT_LOG:系统事件信息
DUMPSYS_ACTIVITY:AMS日志
DUMPSYS_DBINFO :数据库操作记录
DUMPSYS_WINDOW :窗口记录,记录anr发生的窗口页面
PROCESS_STATE: 发生ANR程序的进程信息,虚拟机信息?
SWT_JBT_TRACES:不知道怎么描述,自认为堆栈调用相关的记录吧
SYS_ANDROID_EVENT_LOG:事件log,如ActivityManager、powerManager相关的活动记录
SYS_ANDROID_LOG: 上层app以及framework相关活动的log
SYS_ANDROID_RADIO_LOG: 联网相关
//binder相关
SYS_BINDER_INFO:
SYS_BINDER_MEM_USED:
//parser解析文件夹,基本解析了anr相关的信息,CPU、内存的诊断,binder分析
parser
log很多没有列举全,常用的基本就这些,主要的log文件大概了解一下。首先ZZ_INTERNAL、__exp_main.txt文件中找到ANR的应用进程号、进程名, SYS_ANDROID_EVENT_LOG中记录的事件log中过滤am_anr,并且要对应上进程号,这个事件log基本就指出当前在哪个类或者是什么操作,你就可以看看自己的代码了
SWT_JBT_TRACES同样找到对应的进程号,看看Heap是否不够用了、线程是否被Blocked。一般线程被Blocked了,通常cpu的占用比较低。
Blocked就一定有被持有的对象,这个有时候是发生在binder,就需要分析binder相关的log
binder分配使用情况、诊断分析报告
am_anr: 描述了anr发生时的事件,原因,进程。如果是自己的代码导致的,这里会输出你代码具体的调用类
ANR in:main_log 或 Sys_log ,描述anr发生的应用模块
Reason:原因
Android time :时间 ,有些时候需要根据时间到其他模块追踪问题
上面这三个字段都会同时出现
Blocked | - locked | waiting to lock | held by thread
//发生在死锁的情况下就会出现这三个关键字段
根据官网论述,死锁现象会作为ANR的一种表现方式出现,也会把日志记录到/data/anr目录下,但是如果死锁发生在system server进程中,则看门狗(watchdog)会杀掉system server进程,并且在main log中会看到关键字"WATCHDOG KILLING SYSTEM PROCESS",系统运行时将会重启,system server也会重启,此时用户会看到开机动画会再次播放。
综上所属总结了一条快速过滤命令,一般稳定性测试结束上传的log非常大,解压后几个G的log文件是很正常,不可能打开搜索,即便时间多的用不完,打开的速度正常人也没法忍受,所以直接linux命令过滤,之后再打开那个已经过滤输出的文件,命令如下:
grep -rn "ANR in\|ANRManager: Reason:\|ANRManager: Android time\|am_anr\|WATCHDOG KILLING SYSTEM PROCESS\| iowait\| Blocked\| held by thread\| waiting to lock"
高效的过滤命令,虽然grep很好用,文件超大的时候还是ag速度快
ag "ANR in|ANRManager: Reason:|ANRManager: Android time|am_anr|WATCHDOG KILLING SYSTEM PROCESS| iowait| Blocked| held by thread| waiting to lock"
ag的安装
可以用过滤mobilelog文件夹进行分析,不同的厂商系统日志输出的地方不一样。
ANR出现时调用的函数(源码)
// 记录ANR event log
EventLog.writeEvent(EventLogTags.AM_ANR, app.userId, app.pid,app.processName, app.info.flags, annotation);
// 记录ANR 到 main log
StringBuilder info = new StringBuilder();
info.setLength(0);
info.append("ANR in ").append(app.processName); //关键字"ANR in"
if (activity != null && activity.shortComponentName != null) {
info.append(" (").append(activity.shortComponentName).append(")");
}
info.append("\n");
info.append("PID: ").append(app.pid).append("\n");
if (annotation != null) {
info.append("Reason: ").append(annotation).append("\n");
}
if (parent != null && parent != activity) {
info.append("Parent: ").append(parent.shortComponentName).append("\n");
}
final ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);
// dump堆栈log
File tracesFile = dumpStackTraces(true, firstPids, processCpuTracker, lastPids,
NATIVE_STACKS_OF_INTEREST);