选择哪种工具,需要看具体的场景。我来汇总一下,
如果需要分析Native代码的耗时,可以选SimplePerf;
如果想分析系统调用,可以选择Systrace;
如果想分析整个程序执行流程的耗时,可以选择Traceview或者插桩版本的Systrace。
卡顿监控
消息队列
通过一个监控线程,每隔1秒向主线程消息队列的头部插入一条空消息。假设1秒后这个消息并没有被主线程消费掉,说明阻塞消息运行的时间在0~1秒之间。换句话说,如果我们需要监控3秒卡顿,那在第四次轮询中头部消息依然没有被消费的话,就可以确定主线程出现了一次3秒以上的卡顿。
替换Looper的Printer实现
基于消息队列的卡顿并不准确,正在运行的函数可能不是真正耗时的函数。
解释一下,我们假设消息循环里面顺序执行了A、B、C三个函数,当整个函数执行超过3秒时,因为函数A和B已经执行完毕,我们只能得到正在执行的函数C的堆栈,事实上它可能并不耗时。
不过对于线上大数据来说,因为函数A和B相对比较耗时,所以抓取到它们的概率会更大一些。
插桩方案
在编译过程中插桩,在函数的入口和出口出插桩,兼容性没有问题。需要考虑:
1.避免方法数暴增。 在函数的入口和出口应该插入相同的函数,在编译时提前给每一个方法分配一个独立的ID作为参数。
2.过滤简单的函数。 过滤一些类似return、i++这样的简单函数,并且支持黑名单配置。对一些调用非常频繁的函数,需要添加到黑名单中来降低整个方案对性能的损耗。
基于性能的考虑,线上只会监控主线程的耗时,微信的Matrix使用的就是这个方案,并且只在灰度包中使用
插桩方案的短板是,因为它只能监控应用内自身的函数耗时,无法监控到系统函数调用,整个堆栈看起来好像缺失了一部分。
监控帧率、组件的生命周期、线程
帧率在发生绘制时监控,ViewTreeObserver.addOnDrawListener
卡顿分析
Java实现
- Thread.getState方法获取线程状态
WAITIING、TIME_WAITING和BLOCKED都是需要特别注意的状态。
synchronized (object) { //在这里卡住 -> BLOCKED
object.wait(); //在这里卡住 -> WAITIING
}
当一个线程进入WAITIING状态时,它不仅会释放CPU资源,还会将持有的object锁同时释放
2.通过Thread.getAllStackTraces()进一步拿所有线程的堆栈
需要注意在Android7.0 , getAllStackTraces是不会返回主线程的堆栈的
SIGQUIT信号实现
利用系统ANR的生成机制,步骤:
1.当监控到主线程卡顿时,主动向系统发送SIGQUIT信号。
2.等待/data/anr/traces.txt文件生成
3.文件生成后进行上报
Hook实现
用SIGQUIT信号量获取ANR日志,从而拿到所有线程的信息,这套方案看起来很美好,实际上它存在以下问题:
- 可行性。在很多高版本系统已经没有权限读取/data/anr/traces.txt文件
- 性能。获取所有线程堆栈及各种信息非常耗时,可能进一步加剧用户的卡顿
通过以下方式实现,模拟系统打印ANR日志的流程
- 通过libart.so、dlsym调用ThreadList::ForEach方法,拿到所有的Native线程对象
- 遍历线程对象列表,调用Thread::DumpState方法考虑到兼容性,通过fork子进程方式实现,这样子进程崩溃不会影响主进程运行
为了降低上报数据量,只关心主线程的Java线程状态是WAITING、TIME_WAITING 或者 BLOCKED 几种
卡顿日志分析
推荐使用卡顿树的方式,对于超过3秒的卡顿,具体是多少秒,这涉及到手机性能和当时环境。我们决定抛弃具体的耗时,只按照相同堆栈出现的比例来聚合。这样,我们从一颗树上面,就可以看出哪些堆栈出现的卡顿问题更多,它下面又存在哪些分支