BlockCanary原理解析

BlockCanary

预善其事必先利其器,blockcancary是一个卡顿检测框架

如果卡顿说明,我们在主线程做了一下耗时操作

如果了解 handle原理,就可以知道Android应用程序的主线程消息循环(Message Loop)会通过 dispatchMessage() 方法处理消息。主线程的消息循环是由 Looper 类管理的,它负责接收消息并将其分发给对应的处理器(Handler)进行处理。

以下是主线程消息循环的基本工作原理:

  1. 当应用程序启动时,主线程会创建一个 Looper 对象,并调用 Looper.prepare() 方法初始化消息循环。
  2. 在消息循环运行之前,需要调用 Looper.loop() 方法,它会不断地从消息队列中取出消息并将其分发给对应的处理器。
  3. 当有消息到达时,Looper 会将消息传递给 Handler 对象,然后调用 dispatchMessage() 方法将消息传递给 HandlerhandleMessage() 方法。
  4. handleMessage() 方法是在主线程中执行的,可以根据消息的类型和内容来执行相应的操作。

所以,如果我们想知道主线程是否卡顿,我们只需要知道消息的执行(dispatchMessage)时间就行了

非常巧的是,Android的sdk为我们预留了方法,在消息处理之前,和处理之后都调用了logging.println()方法

java 复制代码
private static boolean loopOnce(final Looper me,
        final long ident, final int thresholdOverride) {
        .....省略
        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);
        }
 }

这样通过实现自定义的Printer,就可以获取主线程每次消息的执行时间,同时这也是BlockCanary开源库的实现原理,不过由于这个库已经很多年没维护了,内部一些api没有适配高版本的Androidsdk,所以直接依赖无法使用了

这里原理还是很简单的,相比于去把开源库的源码拉下来自己适配,我们也可以写一个简易版的卡顿检测方法

kotlin 复制代码
private fun startBlockTest(){
    val mainThread=Thread.currentThread()
    val handlerThread=HandlerThread("blockTest")
    handlerThread.start()//开启线程
    val handler=Handler(handlerThread.looper)
    val stringBuilder=StringBuilder()
    val runnable= Runnable {
        for (stackTraceElement in mainThread.stackTrace){
            stringBuilder
                .append(stackTraceElement.toString())
                .append("\n")
        }
    }
​
    Looper.getMainLooper().setMessageLogging(object : Printer {
        var start:Boolean=true
        var startTime:Long=0
        override fun println(x: String?) {
            if (start){//dispatch之前
                start=false
                startTime=System.currentTimeMillis()
                handler.postDelayed(runnable,800)
            }else{//dispatch之后
                start=true
                val gap = System.currentTimeMillis() - startTime
                if (gap>=1000) {//耗时超过一秒
                    Timber.tag("block").d("有耗时操作出现" + gap + "ms" + x)
                    Timber.tag("block").d(stringBuilder.toString())
                }
                handler.removeCallbacks(runnable)
                stringBuilder.clear()
            }
        }
    })
​
}

可以看到10多行代码还是很简洁的,直接在application里面调用就行

主要思路呢就是

  • 重写println 方法,
  • 在dispatch 之前记录开始时间,如果耗时超过800ms就导出主线程的线程栈信息
  • 在dispatch之后计算消息执行的耗时,如果事件间隔超过定义的事件间隔就在控制台打印栈信息,以便定位卡顿

q:为什么导出堆信息需要延迟执行?

a:避免卡顿,导出一个线程的栈是需要暂停线程的,只有当时间接近我们定义的卡顿时间才进行导出操作。比如我定义的1000ms算卡顿,800ms进行导出栈信息

实际效果如图所示,在点击事件中进行延迟操作,点击打印的栈信息 能够定位到代码的位置
但是注意,在Jetpack Compose中,点击事件处理不依赖于传统的消息分发机制。相反,Compose使用基于函数的更现代方法来处理点击事件。所以正常来说compose中并没有作用(可以使用asm字节码插桩的方式),所以这就是为什么我使用了post来延迟的原因,但是在传统View体系还是可以的

相关推荐
SRC_BLUE_1736 分钟前
SQLI LABS | Less-39 GET-Stacked Query Injection-Intiger Based
android·网络安全·adb·less
无尽的大道4 小时前
Android打包流程图
android
镭封5 小时前
android studio 配置过程
android·ide·android studio
夜雨星辰4875 小时前
Android Studio 学习——整体框架和概念
android·学习·android studio
邹阿涛涛涛涛涛涛6 小时前
月之暗面招 Android 开发,大家快来投简历呀
android·人工智能·aigc
IAM四十二6 小时前
Jetpack Compose State 你用对了吗?
android·android jetpack·composer
奶茶喵喵叫6 小时前
Android开发中的隐藏控件技巧
android
Winston Wood8 小时前
Android中Activity启动的模式
android
众乐认证8 小时前
Android Auto 不再用于旧手机
android·google·智能手机·android auto
三杯温开水8 小时前
新的服务器Centos7.6 安卓基础的环境配置(新服务器可直接粘贴使用配置)
android·运维·服务器