什么是anr
anr是屏幕输入事件,service,广播,contentProvider长时间得不到响应,然后系统弹出一个提示框引导用户关闭进程的过程
- 输入事件超时(5s),包括键盘和屏幕触摸事件
- service(前台20s,后台200s)
- 广播 当onReceive回调函数内的逻辑执行超过阈值(前台15s,后台60s),将产生ANR,静态注册的广播和有序广播会ANR,动态注册的非有序广播并不会ANR
- contentProvider 默认不会anr,需要调用setDetectNotResponding设置超时时间
anr产生原理
1. 屏幕输入事件
每次产生屏幕输入时间的时候,输入系统会检查当前时间距离上个输入事件的分发时间是否超过预定的阈值,如果超过了,就会认为发送了anr,然后就会触发anr的过程,弹出app无响应弹窗,dump堆栈并且记录到data/anr/trace.txt文件中
2. service,广播,contentProvider
以启动一个service为例,主要经历三个阶段,埋炸弹,拆炸弹,引爆炸弹
埋炸弹: 首先在启动service的时候,会给AMS的内部的handler发送一个延时消息SERVICE_TIMEOUT_MSG,如果在AMS内部的Handler收到这个延时消息,就说明发生了ANR
拆炸弹: AMS校验通过后,就会通知ActivityThread创建service,执行service的onCreate()方法,执行结束后就会使用binder通知AMS创建结束,然后AMS中会把之前Handler延迟发送的SERVICE_TIMEOUT_MSG延迟消息移除,这样就不会触发anr,但是如果这个时候binder线程繁忙,会导致无法及时通知到AMS,导致anr
引爆炸弹: 如果在service的onCreate()方法内部的逻辑太耗时,就会导致没法及时通知AMS service已经创建完成,那么AMS内部的Handler已经发送的SERVICE_TIMEOUT_MSG延迟消息就会被Handler接收和执行,从而触发anr,弹出app无响应弹窗,dump堆栈并且记录到data/anr/trace.txt文件中
什么情况会导致service的创建出现anr?
1 主线程消息队列里面的消息存在耗时行为导致service创建和service的onCreate()方法迟迟得不到执行,也就不能及时通知AMS service创建完成
2 binder线程繁忙导致不能及时通知AMS service创建完成
3 service的onCreate()方法内部的逻辑太耗时导致不能及时通知AMS service创建完成
如何监控anr
线上
腾讯的 Matrix,爱奇艺的 xCrash
他们的原理都是基于对系统的SIGQUIT信号的监听,当有进程发生anr的时候,系统就会发送SIGQUIT信号,表示发生了anr,Matrix在拦截到SIGQUIT信号就会调用AMS提供的一些方法判断当前进程是否处于anr的状态,因为虽然系统发送了SIGQUIT信号,但是有可能是其他进程发生了anr,所以需要再判断一次是不是当前进程发生了anr,如果确定就是当前线程发送了anr,另外,系统有一个SignalCatcher 线程,这个线程会监听SIGQUIT信号,当监听到SIGQUIT信号后会找出发生了anr的进程,dump调用堆栈,调用libc.so的write函数把数据写入到data/anr/trace.txt文件中,而Matrix在确定本进程已经发送anr的情况下会对libc.so的write函数进行hook,把写入到data/anr/trace.txt文件的数据也写入到特定的本地文件中,实现anr日志数据的获取保存
另外,客户端还可以监听内存使用情况,在内存紧张的时候打印内存数据到线上日志中,用于帮助分析anr
线下
抓取data/anr/trace.txt进行分析
如何分析trace文件
trace文件包含了堆内存的使用情况,不同进程CPU的使用情况,各个线程的线程名字和状态(正常运行还是阻塞)以及调用堆栈,有时候从调用堆栈中可以直接看出anr的原因,比如主线程的当前调用堆栈中执行了一些耗时行为,再比如主线程在和其他的子线程抢锁导致主线程阻塞,在比如调用一些系统服务导致的anr,因为调用系统服务需要经过binder,而系统中实现binder跨进程通信的线程只有16个,如果其他进程占用了所有的binder线程,那么当前进程只能等待其他进程释放binder线程,这个时候就可能导致当前进程anr,但有时候看trace日志看不出来anr的原因,因为anr可能是其他进程对内存或者CPU消耗太大造成的,这个时候就需对内存情况或者CPU使用情况进行分析,另外,主线程是基于Handler消息轮训机制来执行的,假如主线程的消息队列中有a,b,c消息,a和b消息的内部逻辑是耗时的,c消息的内部逻辑是不耗时的,但是a和b已经执行结束了,这个时候在主线程执行c消息,但此时发送了anr,那么在trace文件中记录的主线程的调用堆栈只会记录c消息内的逻辑的调用堆栈,不会记录a消息和b消息的调用堆栈,所以这个时候看不出来anr的原因的,这个需要就需要有一个稳定的性能监控框架记录每个消息的执行耗时和调用堆栈,在anr发生的时候可以对这些真正耗时的代码逻辑
anr产生的原因
1 其他的进程消耗了大量的CPU,导致当前进程没法获取到足够的CPU时间资源导致anr
2 内存不足,会导致频繁的内存交换和GC,导致anr
3 主线程存在耗时行为
4 主线程在和其他的线程抢锁
5 系统的binder通信线程(16个)都被其他进程占用,导致当前进程无法获取到binder线程,从而不能调用系统服务,导致anr
如何解决和避免anr
1 避免在主线程的耗时行为
2 多线程编程的时候,避免主线程和其他线程抢锁
3 注意内存的使用,避免内存占用过大的问题
参考
ANR 触发、监控、分析 一网打尽_use 版本触发桌面anr-CSDN博客
【转载】干货:ANR 日志分析全面解析一、概述 解决 ANR 一直是 Android 开发者需要掌握的重要技巧,一般从三 - 掘金
稳定性优化:ANR监控方案在程序发生 ANR 时,系统会弹出 ANR 的弹窗,并将 ANR 日志信息写入到 /data/ - 掘金
app卡顿系列四 :今日头条卡顿监控方案落地「这是我参与11月更文挑战的第4天,活动详情查看:2021最后一次更文挑战」 - 掘金