0%

卡顿检测 - BlockCanary浅析

BlockCanary检测卡顿基本原理

  • 利用Looper.setMessageLogging()设置自定义的Printer。
  • 主Handler一条消息处理前后会分别调用Printer的方法。
  • 在消息处理前做线程堆栈采样,消息处理完时结束采样。
  • 如果一条消息处理时间过长,就认为是卡顿,上报采样结果

BlockCanary缺点?

  • 采样数据占用一定CPU资源。
  • 采样的堆栈并不准确,有可能遗漏真正发生卡顿的代码堆栈。

监控触发的逻辑在哪?

  1. 通过调用静态方法 BlockCanary.install(Context context, BlockCanaryContext blockCanaryContext) 获取BlockCanary实例
  2. 调用BlockCanary.start()

BlockCanary.start()里面做了什么?

主要就是

Looper.getMainLooper().setMessageLogging(mBlockCanaryCore.monitor);

这里利用的原理就是Looper的loop方法中在处理一个消息的前后会调用Printer的println方法,这样就可以计算处理一个主线程消息的耗时是多少了

mBlockCanaryCore在哪初始化的?

mBlockCanaryCore在BlockCanary的构造函数中通过BlockCanaryInternals.getInstance()赋值,是BlockCanaryInternals类型

BlockCanary什么时候被创建的?

在BlockCanary.install()中会调用BlockCanary.get()创建BlockCanary单例

mBlockCanaryCore.monitor什么时候创建的?

mBlockCanaryCore是BlockCanaryInternals类型

在BlockCanaryInternals构造函数里创建了LooperMonitor对象并赋值给monitor属性

BlockCanaryInternals.getInstance()会调用BlockCanaryInternals的构造函数创建BlockCanaryInternals实例

时序逻辑是

BlockCanary.install() -> BlockCanaryInternals.getInstance() -> new LooperMonitor()

LooperMonitor做了什么?

LooperMonitor实现了android.util.Printer接口,实现了println()方法

在Looper.loop()处理一个消息前调用startDump(),处理完一个消息后调用stopDump()

特别的,在处理完一个消息后通过isBlock()判断是否发生了卡顿,发生卡顿则调用notifyBlockEvent()

LooperMonitor的startDump()和stopDump()在做什么?

startDump()中分别调用了BlockCanaryInternals中stackSampler和cpuSampler的start()方法

stopDump()中分别调用了BlockCanaryInternals中stackSampler和cpuSampler的stop()方法

stackSampler在做什么?

StackSampler和CpuSampler都继承了AbstractSampler,start()和stop()逻辑是一样的,即每隔一小段时间就去执行doSample()方法,doSample()两个类的实现不一样

StackSampler的doSample()做的是:

  1. 通过Thread.getStackTrace()收集主线程的堆栈信息,把StackTraceElement[]转换为String
  2. 把堆栈String保存在一个LinkedHashMap中,key为执行doSample()时的时间戳,容量满时删除最早插入的元素

cpuSampler在做什么?

CpuSample的doSample()做的是:

  1. 读取/proc/stat 和 /proc/[pid]>/stat 文件中的信息,拼接为一个String
  2. 把CPU信息String保存在一个LinkedHashMap中,key为执行doSample()时的时间戳,容量满时删除最早插入的元素

/proc/stat 和 /proc/[pid]>/stat 有什么区别?

  • /proc/stat 节点记录的是系统进程整体的统计信息。
  • /proc/[pid]>/stat 用于获取某一个进程的统计信息

LooperMonitor的notifyBlockEvent()在做什么?

调用了BlockListener的onBlockEvent(long realTimeStart, long realTimeEnd, long threadTimeStart, long threadTimeEnd)

BlockListener只有一个实现,在BlockCanaryInternals构造函数中创建LooperMonitor时实现的。

首先调用StackSampler的getThreadStackEntries(realTimeStart, realTimeEnd)获取指定时间段的采样堆栈,这个时间段其实就是Looper.loop()中处理一个消息前后的时间区间,相当于获取了发生卡顿期间的采样堆栈。

然后再生成一个BlockInfo对象,把卡顿期间的采样堆栈放进去,再填入一些辅助诊断的信息。

通过LogWriter.save()保存BlockInfo对象到文件中。

再把BlockInfo对象传递给注册的BlockInterceptor对象。

哪些对象实现了BlockInterceptor?

一个是BlockCanaryContext

该类作用是配置上下文,可配置id、当前网络信息、卡顿阈值、log保存路径等。建议:通过自己实现继承该类的子类,配置应用标识符,用户uid,网络类型,卡顿判断阀值,Log保存位置等,可通过继承该类将卡顿信息收集上传云端或保存本地等。

一个是DisplayService

该类会发送一个通知,点击通知会跳转到DisplayActivity,把blockInfo.timeStart传递给DisplayActivity。

DisplayActivity做了什么?

DisplayActivity的onResume()中调用了LoadBlocks.load(this)从本地日志文件中加载出一个List<BlockInfoEx>对象赋值给DisplayActivity的mBlockInfoEntries。

然后拿通过Intent传递过来的mBlockStartTime,在mBlockInfoEntries中寻找是否有匹配的BlockInfoEx,如果有匹配的,就展示这个堆栈的详细信息,没有匹配就现实一个列表展示读取到的日志文件。