今天我们来说说BlockCanary的核心原理,BlockCanary是一个能检测主线程是否卡顿的框架。
那么它为什么可以检测主线程卡顿呢?我们都知道Android是一个消息驱动型的系统。每当我们创建一个进程的时候,在ActivityThread内部都会为我们去创建一个主线程的Looper对象和Handler,然后开启消息的队列的轮询。
**
**
public static void main(String[] args) {
//创建主线程的Looper对象
Looper.prepareMainLooper();
......
......
//创建与主线程绑定的Handller
if (sMainThreadHandler == null) {
//thread.getHandler()返回的成员变量mH
sMainThreadHandler = thread.getHandler();
}
if (false) {
//BlockCanary的核心原理其实就这个,so easy马上来说
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
//开启轮询
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
}
我们以Activity为例,我们知道Activity的所有的生命周期都是运行在主线程的,为什么呢?
我们来看看哈。
ActivityThread内部成员变量mH,这个mH看下代码其实就是个Handler,它的类型是有ActivityThread的内部类H决定的。
我们来看看这个H的源码。
private class H extends Handler {
//定义了各种消息类型
public static final int LAUNCH_ACTIVITY = 100;
public static final int PAUSE_ACTIVITY = 101;
public static final int PAUSE_ACTIVITY_FINISHING= 102;
public static final int STOP_ACTIVITY_SHOW = 103;
public static final int STOP_ACTIVITY_HIDE = 104;
public static final int RESUME_ACTIVITY = 107;
public static final int SEND_RESULT = 108;
public static final int RESUME_ACTIVITY = 107;
public static final int SEND_RESULT = 108;
public static final int DESTROY_ACTIVITY = 109;
public static final int BIND_APPLICATION = 110;
public static final int EXIT_APPLICATION = 111;
public static final int NEW_INTENT = 112;
public static final int RECEIVER = 113;
public static final int CREATE_SERVICE = 114;
public static final int SERVICE_ARGS = 115;
......
.....
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
//启动Activity 最后会回调Acitivty的onCreate方法。
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
......
} break;
case RELAUNCH_ACTIVITY: {
....
ActivityClientRecord r = (ActivityClientRecord)msg.obj;
handleRelaunchActivity(r);
.....
} break;
case PAUSE_ACTIVITY: {
.......
//启动Activity 最后会回调Acitivty的onPause方法。
handlePauseActivity((IBinder) args.arg1, false,
(args.argi1 & USER_LEAVING) != 0, args.argi2,
(args.argi1 & DONT_REPORT) != 0, args.argi3);
.......
} break;
...........
...........
}
}
看了H的代码,大多数朋友肯定都已经知道了。为什么Activity,Service这些组件的生命周期都是运行在主线程呢。其实都是与这个mH息息相关的。这个mH是与主线程的Looper对象进行绑定的,通过这个mH发送过来的消息都是执行在主线程的。Activity,Service这些组件的生命周期方法都是在mH的handlerMessage方法内部进行回调。这里解释一下哈,Android中我们所说的主线程其实就是ActivityThread的main方法所执行的线程,并不是ActivityThread本身哦。
所以换句话说检测主线程卡顿,我们只要检测每个消息执行的时长就可以了。Android内部其实已经为我们提供了一个方法,这就是BlockCanary的原理哦。
Handler机制我们最熟悉不过了,就不多说了,我们直接看看Looper.loop()方法实现,其中必有蹊跷。
public static void loop() {
.......//省略部分代码
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// 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);
}
try {
//分发消息,处理消息。target就是发送消息的Handler
msg.target.dispatchMessage(msg);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
.......//省略部分代码
if (logging != null) {
//消息处理结束第二次打印
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
}
}
这里我们看到了重要的东西了哈,消息处理前会调用一个logging.println()方法,消息分发给Handler处理结束后,还会=调用一次logging.println()方法。这两次println()方法调用的时间间隔,不就是是我们主线程一条消息处理的时长么?那我们只要把logging这个对象替换程我们自己的不就好了嘛?
前面ActivityThread 的main方法中有一句这个代码: Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, “ActivityThread”));
这个方法干嘛的?其实就是set这个logging对象的!
我们来看看这个句代码:
public void setMessageLogging(@Nullable Printer printer) {
//直接把传进来的值,赋值给mLogging对象了
mLogging = printer;
}
下面我们来看看BlockCanary的代码是怎么实现的。
我们只看两处代码哈。
第一处:
BlockCanary的start方法
public void start() {
if (!mMonitorStarted) {
mMonitorStarted = true;
//赋值给Looper中的mLogging,然后就可以监测println方法了。
//是不是与ActivtiyThread中的代码一样呢
//mBlockCanaryCore.monitor 是一个内部定义的LooperMonitor对象
Looper.getMainLooper().setMessageLogging(mBlockCanaryCore.monitor);
}
}
第二处:
@Override
public void println(String x) {
//根据Looper的loop方法源码,我们可以知道这边x可以打印出发送消息的Handler。
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;
}
到这里我相信大家应该都清楚了,这不就是跟我们上面说的那个方法一致嘛。计算两次println的间隔是否大于我们定义的阈值不久可以知道哪条消息耗时了嘛!
BlockCanary检测主线程的耗时,其实就是把Looper中的mLogging给替换程自己的。然后每次处理消息的时候系统都会回调两次println方法。只要计算两次println方法的间隔,就能知道我们主线程的操作是否耗时了。