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体系还是可以的

相关推荐
凯文的内存22 分钟前
Android14 OTA升级速度过慢问题解决方案
android·ota·update engine·系统升级·virtual ab
VinRichard26 分钟前
Android 常用三方库
android
Aileen_0v02 小时前
【玩转OCR | 腾讯云智能结构化OCR在图像增强与发票识别中的应用实践】
android·java·人工智能·云计算·ocr·腾讯云·玩转腾讯云ocr
江上清风山间明月5 小时前
Flutter DragTarget拖拽控件详解
android·flutter·ios·拖拽·dragtarget
debug_cat7 小时前
AndroidStudio Ladybug中编译完成apk之后定制名字kts复制到指定目录
android·android studio
编程洪同学11 小时前
Spring Boot 中实现自定义注解记录接口日志功能
android·java·spring boot·后端
氤氲息13 小时前
Android 底部tab,使用recycleview实现
android
Clockwiseee14 小时前
PHP之伪协议
android·开发语言·php
小林爱14 小时前
【Compose multiplatform教程08】【组件】Text组件
android·java·前端·ui·前端框架·kotlin·android studio
小何开发15 小时前
Android Studio 安装教程
android·ide·android studio