BlockCanary是什么
BlockCanary是一个Android平台的一个非侵入式的性能监控组件,应用只需要实现一个抽象类,提供一些该组件需要的上下文环境,就可以在平时使用应用的时候检测主线程上的各种卡慢问题,并通过组件提供的各种信息分析出原因并进行修复。
取名为BlockCanary则是为了向LeakCanary致敬,顺便本库的UI部分是从LeakCanary改来的。
BlockCanary设计原理
上图是BlockCanary的工作流程图,整个流程分为三个部分,分别是监控卡顿、捕获现场、保存现场记录到本地。三个部分分别执行在不同的线程、这也是上述图中三个区域颜色不同的原因,下面详细解释下起工作流程。
第一部分-接收消息
BlockCanary对象构造时,会使用HandlerThread开启两个线程、分别对应图中绿色和紫色两块区域,start方法开启后,会将自定义的Printer设置给主线程的Looper,这样自定义的Printer就能获取每一个message处理时打印的日志,如果不了解Handler机制,👉这里
private static HandlerThreadWrapper sLoopThread = new HandlerThreadWrapper("loop");
private static HandlerThreadWrapper sWriteLogThread = new HandlerThreadWrapper("writer");
第二部分-监控卡顿
当主线程Looper开始工作后,会从消息队列中获取待处理的message,接着会调用logging的println方法,而BlockCanary自定义的Printer就从println开始记录。
public static void loop() {
for (;;) {
Message msg = queue.next();
if (msg == null) {
return;
}
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
}
}
public void println(String x) {
if (!mPrintingStarted) {
mStartTimestamp = System.currentTimeMillis();
mStartThreadTimestamp = SystemClock.currentThreadTimeMillis();
mPrintingStarted = true;
startDump();
} else {
final long endTime = System.currentTimeMillis();
mPrintingStarted = false;
if (isBlock(endTime)) {
notifyBlockEvent(endTime);
}
stopDump();
}
}
private boolean isBlock(long endTime) {
return endTime - mStartTimestamp > mBlockThresholdMillis;
}
首先记录当前消息处理前的时间,标记为mStartTimestamp(对应图中T1),然后通知捕获线程,该线程第一次延时thresold * 0.8,然后开始循环取dump调用栈和cpu信息。此时主线程不受影响,继续处理消息。
第三部分-记录卡顿
消息处理完后,会再次调用logging的println方法,此时记下结束时间(对应图中T2), 然后用T2-T1>mBlockThresholdMillis来判断是否为卡顿,若卡顿,则开始通知日志线程去写入现场日志到本地
private void notifyBlockEvent(final long endTime) {
HandlerThreadFactory.getWriteLogThreadHandler().post(new Runnable() {
@Override
public void run() {
mBlockListener.onBlockEvent(startTime, endTime, startThreadTime, endThreadTime);
}
});
}
BlockCanary讨论
第一次看了BlockCanary的源码的时候,有一些疑惑,后面也想了想,记录在这里。
- 为什么需要延时threshold * 0.8才开始取dump线程堆栈和cpu信息?
因为threshold是我们设定的卡顿时长阈值,这里*0.8可能主要是考虑性能影响,在如果本次方法执行时长小于threshold * 0.8,就不用去dump 堆栈和cpu信息,这样可以减少监控组件本身对cpu的消耗。 - BlockCanary能监控所有卡顿吗?
目前从源代码来看,答案是不能,基于Handler的Message机制可以监控主线程消息的卡顿,但是项目中使用了IdleHandler的情况就监控不到。 - BlockCanary能准确捕捉到卡顿调用栈吗?
不能,比如一个message里有两个方法,第一个方法耗时80ms,第二个耗时30毫秒,卡顿阈值100ms,此时这个message一共耗费了110毫秒,检测到卡顿,但是堆栈捕获到第二个方法调用栈,其实第一个调用栈才是最耗时的。
|