文章目录
一、ANR背景
1.1 ANR种类
Service Timeout: 比如前台服务在20s内未执行完成
Broadcast Timeout: 比如前台广播在10s内未执行完成
ContentProvider Timeout: 内容提供者, 在publish超时10s
InputDispatching Timeout: 输入事件分发超时5s, 包括按键和触摸事件
1.2 ANR具体超时
1. Service<ActiveServices.java> {
1. 前台服务20s int SERVICE_TIMEOUT = 20 * 1000
2. 后台服务200s int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10
}
2. Broadcast<ActivityManagerService.java> {
1. 前台广播10s int BROADCAST_FG_TIMEOUT = 10 * 1000
2. 后台广播60s int BROADCAST_BG_TIMEOUT = 60 * 1000
}
3. InputDispatching<ActivityManagerService.java> {
1. 输入事件5s int KEY_DISPATCHING_TIMEOUT = 5 * 1000
}
1.3 ANR原因
- 1、应用在主线程上执行耗时的I/O操作
- 2、应用在主线程上进行长时间的计算
- 3、主线程在对另一个进程进行同步binder调用, 而后者需要很长时间才能返回
- 4、主线程处于阻塞状态, 主动睡眠或者等待其他线程上的同步方法或者同步代码块执行完毕
- 5、主线程与其他线程之间发生死锁
- 6、
备注:
在Activity.onCreate方法里面调用sleep方法或者说做了耗时操作, 不一定会产生ANR, 其实从ANR本身意为应用程序没有响应, 同时根据总结的ANR的原因可以看出, 耗时操作本身不会产生ANR, 导致ANR的根本还是应用程序无法在一定时间内响应用户的操作, 所以因为主线程被耗时操作占用了, 主线程无法对下一个操作进行响应才会ANR
二、数据收集
ANR日志会写入到/data/anr/trace.txt文件中
三、ANR框架
3.1 ANR-WatchDog
3.1.1 ANR-WatchDog工作流程
3.1.1 ANR-WatchDog优点
ANR分析与数据采集与主线程隔离, 避免主线程被阻塞导致ANR分析也受阻. 这一点优于BlockCanary
3.1.2 ANR-Watch缺点
开启子线程一直运行, 消耗性能
3.2 BlockCanary
3.2.1 BlockCanary原理
3.2.2 BlockCanary优点
比较方便的捕捉到卡顿的堆栈
3.2.3 BlockCanary缺点
无法获取到各个函数的执行耗时, 对于稍微复杂一点的堆栈, 很难找出可能耗时的函数, 也就很难找到卡顿的原因. 而且通过其他线程循环获取主线程的堆栈, 如果稍微处理不及时, 很容易导致获取的堆栈有所偏移, 不够准确, 加上没有耗时信息, 卡顿也就不好定位.
3.3 Matrix-AnrTrace
3.3.1 AnrTrace流程
- 1、函数执行前后获取当前距离MethodBeat模块初始化的时间offset(为了压缩数据, 存进一个long类型变量中), 并将当前执行的MethodBeat i或者o、method id及时间offset, 存放到一个long类型变量中, 记录到一个预先初始化好的数组long[]中index的位置
- 2、dispatchMessage执行之前, 重置一个定时器, 如果5s内没有cancel, 则认为发生了ANR, 这时会主动取出当前记录的buffer数据进行独立分析上报, 对这种ANR事件进行单独监控及定位
- 3、考虑到每个方法执行前后都获取系统时间会对性能影响比较大, 而实际上, 单个函数执行耗时小于5ms的情况, 对卡顿来说不是主要原因, 可以忽略不计. 如果是多次调用的情况, 则在它的父级方法中可以反映出来, 所以为了减少对性能的影响, 通过另一条线程每5ms去更新一个时间变量, 而每个方法执行前后只读取该变量来减少性能损耗
- 4、
堆栈聚类问题:
如果将收集的原始数据进行上报, 数据量很大而且后台很难聚类有问题的堆栈, 所以在上报之前需要对采集的数据进行简单的整合及裁剪, 并分析出一个能代表卡顿堆栈的key, 方便后台聚合 - 5、通过遍历采集的buffer, 相邻i与o为一次完整函数执行, 计算出一个调用树及每个函数执行耗时, 并对每一级中的相同执行函数做聚合, 最后通过一个简单策略, 分析出主要耗时的那一级函数, 作为代表卡顿堆栈的key.
四、卡顿一整套解决方案
- 1、线下使用Android Studio自带工具Profiler检测卡顿模块的方法耗时、以及CPU使用率情况, 找出卡顿地方对代码进行优化, 例如将IO操作放在子线程中
- 2、使用自动化工具xCrash捕获ANR日志
- 3、使用BlockCanary方案设置自定义Printer
- 4、关键模块方法插桩, 方法执行前后记录方法耗时, 当Printer检测到方法耗时存在时, 从方法耗时集合中截取当前段的方法耗时情况
具体思路是将数组转化为多叉树结构