BlockCanary是什么?
BlockCanary是国内开发者MarkZhai开发的一套性能监控组件,主要通过监控Handler
中的dispatchMessage
过程所消耗的时间是否超过阀值来判断是否发生卡顿。
检测原理
界面卡顿主要是因为消息分发处理的不及时导致的,Android的消息分发机制主要是由Message/Looper/Handler
构建的,不熟悉Message/Looper/Handler
可以参考我之前的文章 Android消息循环机制浅析
由于主线程只存在一个Looper
,并且Android系统所有更新UI的操作都是在主线程里面执行的.
因此所有的UI操作都会经过主线程的Looper
消息循环。
其中Looper#loop
中有那么段代码
public static void loop() {
...
for (;;) {
...
//默认为null,可通过setMessageLogging()方法来指定输出,用于debug功能
Printer logging = me.mLogging;
if (logging != null) {
//事件分发之前的时间T1
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);
if (logging != null) {
//事件分发之后的时间T2
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
...
}
}
如果T2-T1
的时间差大于某个阀值,就可以判断发生了卡顿,在BlockCanary
中,阀值是3000ms
。超过阀值之后,BlockCanary
就会将一些必要的log日志输出.输出的展现方法类似leakCanary
另外要注意的一点是能不能检测到卡顿主要看对应的事件经不经过Handler
分发,
在issues
中,有人提到用adb input keyevent E
的方法让主线程休眠30s,这时BlockCanary
就不能检测出ANR的问题,因为模拟按键缺少Handler
的分发。
具体讨论在issues
基本使用
- 引用
dependencies {
compile 'com.github.markzhai:blockcanary-android:1.5.0'
// 仅在debug包启用BlockCanary进行卡顿监控和提示的话,可以这么用
debugCompile 'com.github.markzhai:blockcanary-android:1.5.0'
releaseCompile 'com.github.markzhai:blockcanary-no-op:1.5.0'
}
- 初始化
public class DemoApplication extends Application {
private static Context sContext;
@Override
public void onCreate() {
super.onCreate();
sContext = this;
BlockCanary.install(this, new AppContext()).start();
}
public static Context getAppContext() {
return sContext;
}
}
源码解读
BlockCanary.install(this, new AppContext()).start();
初始化过程实际上做了两件事情,install
和start
install
public static BlockCanary install(Context context, BlockCanaryContext blockCanaryContext) {
//这里保存applicationContext和用户设置的参数
//其中所有的配置参数都可以自定义BlockCanaryContext的子类来实现
BlockCanaryContext.init(context, blockCanaryContext);
//开启或者关闭DisplayActivity视图以账号堆栈信息,这里会单开一个io信息来存储堆栈日志
setEnabled(context, DisplayActivity.class, BlockCanaryContext.get().
displayNotification());
return get();
}
install
最后的get()方法
会创建BlockCanary
//
public static BlockCanary get() {
if (sInstance == null) {
synchronized (BlockCanary.class) {
if (sInstance == null) {
sInstance = new BlockCanary();
}
}
}
return sInstance;
}
//核心类BlockCanaryInternals拦截器,作用整个block流程
private BlockCanary() {
BlockCanaryInternals.setContext(BlockCanaryContext.get());
mBlockCanaryCore = BlockCanaryInternals.getInstance();
mBlockCanaryCore.addBlockInterceptor(BlockCanaryContext.get());
//DisplayService只在开启了通知栏时才会添加,当卡顿发生时将通过DisplayService发起通知栏消息,调起
//DisplayActivity来展示堆栈信息
if (!BlockCanaryContext.get().displayNotification()) {
return;
}
mBlockCanaryCore.addBlockInterceptor(new DisplayService());
}
BlockCanaryInternals
public BlockCanaryInternals() {
stackSampler = new StackSampler(
Looper.getMainLooper().getThread(),
sContext.provideDumpInterval());
cpuSampler = new CpuSampler(sContext.provideDumpInterval());
setMonitor(new LooperMonitor(new LooperMonitor.BlockListener() {
@Override
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);
}
}
}
}
}, getContext().provideBlockThreshold(), getContext().stopWhenDebugging()));
LogWriter.cleanObsolete();
}
- stackSampler 表示线程堆栈采样
- CpuSampler 表示Cpu相关数据采样
- LooperMonitor的
onBlockEvent
函数会在发生block事件的时候,将相关的log输出 - BlockInfo表示block各类数据的集合
start过程
public void start() {
if (!mMonitorStarted) {
mMonitorStarted = true;
//设置Looper对象在分发消息的时候打印debug日志,传入的是之前定义的LooperMonitor对象
Looper.getMainLooper().setMessageLogging(mBlockCanaryCore.monitor);
}
}
接下来主要看LooperMonitor
的println
函数,因为主线程的消息分发在dispatchMessage
前后分别会打印一次日志,
public void println(String x) {
if (mStopWhenDebugging && Debug.isDebuggerConnected()) {
return;
}
if (!mPrintingStarted) {
mStartTimestamp = System.currentTimeMillis();
//SystemClock.currentThreadTimeMillis() 表示线程处于running状态的时候,如果现场进入休眠状 //态,这个函数是不会计入时间的
mStartThreadTimestamp = SystemClock.currentThreadTimeMillis();
mPrintingStarted = true;
startDump();
} else {
final long endTime = System.currentTimeMillis();
mPrintingStarted = false;
if (isBlock(endTime)) {
notifyBlockEvent(endTime);
}
stopDump();
}
}
- 在
dispatchMessage
前执行一次println
方法,记录开始时间并调用startDump
记录堆栈信息 - 在
dispatchMessage
后在执行一次println
方法,并对比执行时间,
if (isBlock(endTime)) {
notifyBlockEvent(endTime);
}
private boolean isBlock(long endTime) {
return endTime - mStartTimestamp > mBlockThresholdMillis;
}
private void notifyBlockEvent(final long endTime) {
//注意这里需要重新创建long对象来对时间进行赋值,不然会因为浅拷贝的问题导致时间错落
final long startTime = mStartTimestamp;
final long startThreadTime = mStartThreadTimestamp;
final long endThreadTime = SystemClock.currentThreadTimeMillis();
HandlerThreadFactory.getWriteLogThreadHandler().post(new Runnable() {
@Override
public void run() {
mBlockListener.onBlockEvent(startTime, endTime, startThreadTime, endThreadTime);
}
});
}
对比的逻辑十分简单,结束时间大于开始时间大于预先设置的阀值,即可理解发生block
,这时调用notifyBlockEvent
,将发生block
的时间信息回传给BlockCanaryInternals
public BlockCanaryInternals() {
...
setMonitor(new LooperMonitor(new LooperMonitor.BlockListener() {
@Override
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();
//将发生block时的线程堆栈和cpu堆栈记录下来
LogWriter.save(blockInfo.toString());
//回调给DisplayService,DisplayService的逻辑很简单,创建一个NotificationManager //对象,点击事件设置成跳转到DisplayActivity
if (mInterceptorChain.size() != 0) {
for (BlockInterceptor interceptor : mInterceptorChain) {
interceptor.onBlock(getContext().provideContext(), blockInfo);
}
}
}
}
}, getContext().provideBlockThreshold(), getContext().stopWhenDebugging()));
...
}
概括
- 自定义一个
Looper
的MessageLogging
设置给主线程的Looper - 在
Looper.loop
的dispatchMessage
方法前打印线程和CPU的堆栈信息 - 在
Looper.loop
的dispatchMessage
方法后判断是否发生block
- 发生
block
时调用DisplayService
创建NotificationManager
消息通过 - 点击
NotificationManager
窗口跳转到DisplayActivity
,并展示发生block
时的线程堆栈以及CPU堆栈
参考文章
Android UI卡顿监测框架BlockCanary原理分析