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

相关推荐
移动开发者1号2 小时前
使用 Android App Bundle 极致压缩应用体积
android·kotlin
移动开发者1号2 小时前
构建高可用线上性能监控体系:从原理到实战
android·kotlin
ii_best7 小时前
按键精灵支持安卓14、15系统,兼容64位环境开发辅助工具
android
美狐美颜sdk7 小时前
跨平台直播美颜SDK集成实录:Android/iOS如何适配贴纸功能
android·人工智能·ios·架构·音视频·美颜sdk·第三方美颜sdk
恋猫de小郭12 小时前
Meta 宣布加入 Kotlin 基金会,将为 Kotlin 和 Android 生态提供全新支持
android·开发语言·ios·kotlin
aqi0013 小时前
FFmpeg开发笔记(七十七)Android的开源音视频剪辑框架RxFFmpeg
android·ffmpeg·音视频·流媒体
androidwork15 小时前
深入解析内存抖动:定位与修复实战(Kotlin版)
android·kotlin
梦天201515 小时前
android核心技术摘要
android
szhangbiao17 小时前
“开发板”类APP如果做屏幕适配
android
高林雨露18 小时前
RecyclerView中跳转到最后一条item并确保它在可视区域内显示
android