源码角度聊聊BlockCanary的原理

15 篇文章 4 订阅
4 篇文章 0 订阅

今天我们来说说BlockCanary的核心原理,BlockCanary是一个能检测主线程是否卡顿的框架。
那么它为什么可以检测主线程卡顿呢?我们都知道Android是一个消息驱动型的系统。每当我们创建一个进程的时候,在ActivityThread内部都会为我们去创建一个主线程的Looper对象和Handler,然后开启消息的队列的轮询。

**

1.ActivityThead.main()

**

    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");
    }
}

2.ActivityThread的内部类H

我们以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的原理哦。

3.Looper.loop()

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;
}

4.BlockCanary的实现

下面我们来看看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的间隔是否大于我们定义的阈值不久可以知道哪条消息耗时了嘛!

5.总结

BlockCanary检测主线程的耗时,其实就是把Looper中的mLogging给替换程自己的。然后每次处理消息的时候系统都会回调两次println方法。只要计算两次println方法的间隔,就能知道我们主线程的操作是否耗时了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值