
流程图
BlockCanary启动一个线程负责保存UI线程当前堆栈信息,将堆栈信息以及CPU信息保存分别保存在 mThreadStackEntries和mCpuInfoEntries中,每条信息都以时间撮为key保存。
BlockCanary注册了logging来获取事件开始结束时间。如果检测到事件处理时间超过阈值(默认值1s),则从mThreadStackEntries中查找T1T2这段时间内的堆栈信息,并且从mCpuInfoEntries中查找T1T2这段时间内的CPU及内存信息。并且将信息格式化后保存到本地文件,并且通知用户。
该组件利用了主线程的消息队列处理机制,通过
Looper.getMainLooper().setMessageLogging(mainLooperPrinter);
并在mainLooperPrinter中判断start和end,来获取主线程dispatch该message的开始和结束时间,并判定该时间超过阈值(如2000毫秒)为主线程卡慢发生,并dump出各种信息,提供开发者分析性能瓶颈。
...
@Override
public void println(String x) {
if (!mStartedPrinting) {
mStartTimeMillis = System.currentTimeMillis();
mStartThreadTimeMillis = SystemClock.currentThreadTimeMillis();
mStartedPrinting = true;
} else {
final long endTime = System.currentTimeMillis();
mStartedPrinting = false;
if (isBlock(endTime)) {
notifyBlockEvent(endTime);
}
}
}
private boolean isBlock(long endTime) {
return endTime - mStartTimeMillis > mBlockThresholdMillis;
}
...
四、源码解读
BlockCanary.install(this, new AppBlockContext()).start();
首先我们看看他的入口,install这个方法:
/**
* Install {@link BlockCanary}
*
* @param context Application context
* @param blockCanaryContext BlockCanary context
* @return {@link BlockCanary}
*/
public static BlockCanary install(Context context, BlockCanaryContext blockCanaryContext) {
BlockCanaryContext.init(context, blockCanaryContext);
setEnabled(context, DisplayActivity.class, BlockCanaryContext.get().displayNotification());
return get();
}
这里调用三行代码:
- 调用init()方法, 记录
Application和BlockCanaryContext, 为后面的处理提供上下文Context和配置参数(例如: 卡顿阈值,是否显示通知 等等…) - 调用setEnabled()方法, 判断桌面是否显示黄色的logo图标
- 调用get()方法, 创建BlockCanary的实例,并且创建BlockCanaryInternals实例, 赋值给mBlockCanaryCore属性, 用来处理后面的流程
static void init(Context context, BlockCanaryContext blockCanaryContext) {
sApplicationContext = context;
sInstance = blockCanaryContext;
}
这个init方法就做了一个赋值的操作,将我们传递过来的context进行赋值。
我们继续看BlockCanary.start()做了什么事:
public void start() {
if (!mMonitorStarted) {
mMonitorStarted = true;
Looper.getMainLooper().setMessageLogging(mBlockCanaryCore.monitor);
}
}
start()方法只做了一件事: 给Looper设置一个Printer
那么当Looper处理消息的前后, 就会调用mBlockCanaryCore.monitor的println()方法。
mBlockCanaryCore.monitor是BlockCanaryInternals的成员属性LooperMonitor
class LooperMonitor implements Printer {
...
@Override
public void println(String x) {
//如果StopWhenDebugging, 就不检测
if (mStopWhenDebugging && Debug.isDebuggerConnected()) {
return;
}
if (!mPrintingStarted) {
mStartTimestamp = System.currentTimeMillis();
mStartThreadTimestamp = SystemClock.currentThreadTimeMillis();
mPrintingStarted = true;
startDump(); //在子线程中获取调用栈和CPU信息
} else {
final long endTime = System.currentTimeMillis();
mPrintingStarted = false;
if (isBlock(endTime)) { //判断是否超过设置的阈值
notifyBlockEvent(endTime);
}
stopDump(); //停止获取调用栈和CPU信息
}
}
//判断是否超过设置的阈值
private boolean isBlock(long endTime) {
return endTime - mStartTimestamp > mBlockThresholdMillis;
}
...
}
LooperMonitor的println()就是最核心的地方, 实现代码也很简单:
- Looper处理消息前, 获取当前时间并且保存, 调用startDump()启动一个任务定时去采集 调用栈/CPU 等等信息
- Looper处理消息完成, 获取当前时间, 判断是否超过我们自定义的阈值isBlock(endTime)如果超过了, 就调用notifyBlockEvent(endTime)来通知处理后面的流程
- 调用stopDump()停止获取调用栈以及CPU的任务
startDump采集的信息包括:
- 基本信息:机型, CPU内核数, 进程名, 内存, 版本号 等等
- 耗时信息:实际耗时, 主线程时钟耗时, 卡顿开始时间和结束时间
- CPU信息:时间段内CPU是否忙, 时间段内的系统CPU/应用CPU占比, I/O占- – CPU使用率
- 堆栈信息:发生卡顿前的最近堆栈
五、总结
blockcanary完美利用了安卓上的消息机制,给Looper设置一个Printer,通过记录堆栈和CPU信息,计算主线程处理消息的时间,如果超过了阈值,就检索此时的堆栈和cpu信息来帮助分析卡顿原因。
BlockCanary — 轻松找出Android App界面卡顿元凶
GitHub:BlockCanary

评论列表