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

相关推荐
666xiaoniuzi4 小时前
深入理解 C 语言中的内存操作函数:memcpy、memmove、memset 和 memcmp
android·c语言·数据库
沐言人生9 小时前
Android10 Framework—Init进程-8.服务端属性文件创建和mmap映射
android
沐言人生9 小时前
Android10 Framework—Init进程-9.服务端属性值初始化
android·android studio·android jetpack
沐言人生9 小时前
Android10 Framework—Init进程-7.服务端属性安全上下文序列化
android·android studio·android jetpack
追光天使9 小时前
【Mac】和【安卓手机】 通过有线方式实现投屏
android·macos·智能手机·投屏·有线
小雨cc5566ru9 小时前
uniapp+Android智慧居家养老服务平台 0fjae微信小程序
android·微信小程序·uni-app
一切皆是定数10 小时前
Android车载——VehicleHal初始化(Android 11)
android·gitee
一切皆是定数11 小时前
Android车载——VehicleHal运行流程(Android 11)
android
problc11 小时前
Android 组件化利器:WMRouter 与 DRouter 的选择与实践
android·java
图王大胜11 小时前
Android SystemUI组件(11)SystemUIVisibility解读
android·framework·systemui·visibility