BlockCanary检测卡顿基本原理
- 利用Looper.setMessageLogging()设置自定义的Printer。
- 主Handler一条消息处理前后会分别调用Printer的方法。
- 在消息处理前做线程堆栈采样,消息处理完时结束采样。
- 如果一条消息处理时间过长,就认为是卡顿,上报采样结果
BlockCanary缺点?
- 采样数据占用一定CPU资源。
- 采样的堆栈并不准确,有可能遗漏真正发生卡顿的代码堆栈。
监控触发的逻辑在哪?
- 通过调用静态方法 BlockCanary.install(Context context, BlockCanaryContext blockCanaryContext) 获取BlockCanary实例
- 调用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()做的是:
- 通过Thread.getStackTrace()收集主线程的堆栈信息,把StackTraceElement[]转换为String
- 把堆栈String保存在一个LinkedHashMap中,key为执行doSample()时的时间戳,容量满时删除最早插入的元素
cpuSampler在做什么?
CpuSample的doSample()做的是:
- 读取/proc/stat 和 /proc/[pid]>/stat 文件中的信息,拼接为一个String
- 把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,如果有匹配的,就展示这个堆栈的详细信息,没有匹配就现实一个列表展示读取到的日志文件。