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

相关推荐
草莓熊Lotso2 小时前
Linux 文件描述符与重定向实战:从原理到 minishell 实现
android·linux·运维·服务器·数据库·c++·人工智能
恋猫de小郭3 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
工程师老罗9 小时前
如何在Android工程中配置NDK版本
android
Libraeking12 小时前
破壁行动:在旧项目中丝滑嵌入 Compose(混合开发实战)
android·经验分享·android jetpack
市场部需要一个软件开发岗位13 小时前
JAVA开发常见安全问题:Cookie 中明文存储用户名、密码
android·java·安全
JMchen12314 小时前
Android后台服务与网络保活:WorkManager的实战应用
android·java·网络·kotlin·php·android-studio
crmscs15 小时前
剪映永久解锁版/电脑版永久会员VIP/安卓SVIP手机永久版下载
android·智能手机·电脑
localbob15 小时前
杀戮尖塔 v6 MOD整合版(Slay the Spire)安卓+PC端免安装中文版分享 卡牌肉鸽神作!杀戮尖塔中文版,电脑和手机都能玩!杀戮尖塔.exe 杀戮尖塔.apk
android·杀戮尖塔apk·杀戮尖塔exe·游戏分享
机建狂魔15 小时前
手机秒变电影机:Blackmagic Camera + LUT滤镜包的专业级视频解决方案
android·拍照·摄影·lut滤镜·拍摄·摄像·录像
hudawei99615 小时前
flutter和Android动画的对比
android·flutter·动画