BlockCanary源码分析
概述
BlockCanary是Android平台上的一个轻量的,非侵入式的性能监控组件,可以在使用应用的时候检测主线程上的各种卡顿问题,并可通过组件提供的各种信息分析出原因并进行修复。
特点
BlockCanary对主线程操作进行了完全透明的监控,并能输出有效的信息,帮助开发分析、定位到问题所在,迅速优化应用。其特点有:
1非侵入式,简单的两行就打开监控,不需要到处打点,破坏代码优雅性。
2精准,输出的卡顿的堆栈信息可以帮助定位到问题所在(精确到行),不需要像Logcat一样,慢慢去找。
原理和思想
在Android中,界面的刷新和事件的发送都需要发送Message消息,然后由Looper 调度分配给主线程主线程,如果中间产生的消息耗时较长, 如果 中间产生了耗时任务,导致界面刷新的消息收不到,画面一直停留在原有的话,从视觉的效果上就产生了卡顿。BlockCanary的思想是在Looper里添加自定义Printer,重写Printer的println()接口。在Looper的loop()方法里调用Printer.println()接口。 在重写的println()里统计耗时时间。通过统计耗时时间以及定义的卡顿的门限来确定主线程是否卡顿。
Looer.loop()
在Andorid应用中,不论有多少个Handler最终都会调用同一个Looper.loop()方法, Message在这里进行排队分发。下面我来来看一下loop()方法代码:
public static void loop() {
// ....
for (;;) {
...
// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
//消息分发前信息打印
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
...
msg.target.dispatchMessage(msg);
...
if (logging != null) {
//消息分发后信息打印
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
...
}
}
通过分析loop()方法我们可以发现在消息消费前和消费后都会通过Printer打印信息。如果替换成自定义Prinert就可以计算消息消费前后的时长。以此确定当前消息是否卡顿。
LooperMonitor
通过上面loop()方法代码分析得到在消息分发前后都会调用Printer类的println()方法打印信息。Blockcanary中将Printer替换成自定义的Printer类LooperMonitor。LooperMonitor类的println()方法代码如下:
public void println(String x) {
if (mStopWhenDebugging && Debug.isDebuggerConnected()) {
return;
}
if (!mPrintingStarted) {
//消息分发前获取时间戳
mStartTimestamp = System.currentTimeMillis();
mStartThreadTimestamp = SystemClock.currentThreadTimeMillis();
mPrintingStarted = true;
startDump();
} else {
//消息分发后获取时间戳
final long endTime = System.currentTimeMillis();
mPrintingStarted = false;
//isBlock()方法就是通过endTime - mStartTimestamp计算耗时比较是否卡顿的。
if (isBlock(endTime)) {
notifyBlockEvent(endTime);
}
stopDump();
}
}
println()代码中会记录下dispatchMessage()调用前后的时间戳,通过计算前后时间戳差值来计算是否卡顿。
如果卡顿就发送卡顿事件。
卡顿信息打印
从上面println()源码分析我们得到,如果产生了卡顿,会发送卡顿通知事件。最终会调用到LooperMonitor中的
mBlockListener.onBlockEvent()方法体中。代码如下:
public void onBlockEvent(long realTimeStart, long realTimeEnd,
long threadTimeStart, long threadTimeEnd) {
// Get recent thread-stack entries and cpu usage
ArrayList<String> threadStackEntries = stackSampler
.getThreadStackEntries(realTimeStart, realTimeEnd);
if (!threadStackEntries.isEmpty()) {
BlockInfo blockInfo = BlockInfo.newInstance()
.setMainThreadTimeCost(realTimeStart, realTimeEnd, threadTimeStart, threadTimeEnd) //打印耗时信息
.setCpuBusyFlag(cpuSampler.isCpuBusy(realTimeStart, realTimeEnd))
.setRecentCpuRate(cpuSampler.getCpuRateInfo())
.setThreadStackEntries(threadStackEntries) //打印调用堆栈
.flushString();
LogWriter.save(blockInfo.toString());
if (mInterceptorChain.size() != 0) {
for (BlockInterceptor interceptor : mInterceptorChain) {
interceptor.onBlock(getContext().provideContext(), blockInfo);
}
}
}
}
从onBlockEvent()可以看出主要打印产生卡顿的各种信息。这里我们主要关心产生卡顿的调用堆栈。
BlockCanary几个核心
LooperMonitor负责统计方法耗时
StackSampler函数执行堆栈采集
CpuSampler式cpu信息采集
结语
blockcanary充分的利用了Loop的机制,在MainLooper的loop方法中执行dispatchMessage前后都会执行printer的println进行输出,并且提供了方法设置printer。通过分析前后打印的时差与阈值进行比对,从而判定是否卡顿。