2024年鸿蒙最新BlockCanary 卡顿监测_blockcanary使用(2),2024年最新HarmonyOS鸿蒙面试试题

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

作者:海象

前言

最近在处理项目中的拍摄视频后上传界面卡顿的问题,找到 BlockCanary 这个工具来定位,由于不支持高版本 Android,当时在定位卡顿时先将项目的 targetSdk 版本降下来,当然这不是个长久的办法,打算花一点时间适配下高版本,先过一遍源码流程

网上很多博客只提到适配分区存储和通知栏,好像忽略了一个细节,CPU 的采样"proc" 在高版本 Android 被禁用,原因是系统防止旁路攻击,只允许系统应用访问

初始化

BlockCanary 跟随 App 启动,内部的 BlockCanaryInternal 有添加拦截器的操作,这里应该是控制的核心逻辑

install () 大概做了这些:

  1. BlockCanaryContext 实现了 BlockInterceptor 接口 进行基础设置,比如文件夹名,判断时间等,用于给开发者自定义的
  2. 启动组件,显示图标到桌面

BlockCanary 构造方法中做了:

  1. 创建 BlockCanaryInternal
  2. 添加拦截器到 BlockCanaryInternal

这里主要是为子线程监测做初始化

启动
    public void start() {
        // 往主线程的 looper 里设置 printer
        Looper.getMainLooper().setMessageLogging(mBlockCanaryCore.monitor);
    }

这里就是 BlockCanary 检测的位置,原理是 Looper.loop() 中会在 Looper.dispatchMessage() 执行前后做打印,刚好可以利用这个做执行时长的处理,通过判断是否超过时间,来判断是否发生了卡顿

检测执行时长

检测时长的逻辑位于 LooperMonitor,它实现了 Printer 接口

    @Override
    public void println(String x) {

        // 执行前
        if (!mPrintingStarted) {
            // 获取当前时间
            mStartTimestamp = System.currentTimeMillis();
            mStartThreadTimestamp = SystemClock.currentThreadTimeMillis();
            mPrintingStarted = true;
            startDump();
        } else {
            // 执行后
            // 获取当前时间
            final long endTime = System.currentTimeMillis();
            mPrintingStarted = false;
            // 计算是否卡顿,如果发生了则通知
            if (isBlock(endTime)) {
                notifyBlockEvent(endTime);
            }
            stopDump();
        }
    }

    // 根据时间差来判断是否卡顿
    private boolean isBlock(long endTime) {
        return endTime - mStartTimestamp > mBlockThresholdMillis;
    }

先来思考下,如果卡顿已经发生了,我们想要获取哪些信息来定位问题:

  1. 哪个位置发生了卡顿,我觉得这是最最重要的
  2. 发生卡顿的原因,是内存不够导致的,其他地方导致的,这对定位问题比较重要

那这些信息应该是在卡顿后,再去获取吗,还能拿到现场信息嘛? 带着这些问题,来看看 BlockCanary 是怎么做的

来看看 startDump() 中做了什么

    private void startDump() {
       // 分别调用了 StackSampler/CpuSampler 的 start()   
       BlockCanaryInternals.getInstance().stackSampler.start()
       BlockCanaryInternals.getInstance().cpuSampler.start();
    }

这两个 start 都是在子线程中执行的,原因是基类内部有个 HandlerThread ,在这个子线程执行方法 doSample()

获取当前执行的内存堆栈的逻辑就在这里,也是定位卡顿位置的关键

    // StackSampler
    @Override
    protected void doSample() {

        StringBuilder stringBuilder = new StringBuilder();
        // 遍历当前线程(主线程)的所有堆栈,放到 String 里
        for (StackTraceElement stackTraceElement : mCurrentThread.getStackTrace()) {
            stringBuilder
                    .append(stackTraceElement.toString())
                    .append(BlockInfo.SEPARATOR);
        }

        synchronized (sStackMap) {
            if (sStackMap.size() == mMaxEntryCount && mMaxEntryCount > 0) {
                sStackMap.remove(sStackMap.keySet().iterator().next());
            }
            // 保存到 Map 中,最多保存 100 个
            sStackMap.put(System.currentTimeMillis(), stringBuilder.toString());
        }
    }

   // CpuSampler

    protected void doSample() {
        BufferedReader cpuReader = null;
        BufferedReader pidReader = null;
            // 通过 /proc/stat 读取 cpu 参数,这个 Android 高版本中已经被禁用了
            cpuReader = new BufferedReader(new InputStreamReader(
                    new FileInputStream("/proc/stat")), BUFFER_SIZE);
            String cpuRate = cpuReader.readLine();

            if (mPid == 0) {
                mPid = android.os.Process.myPid();
            }
            pidReader = new BufferedReader(new InputStreamReader(
                    new FileInputStream("/proc/" + mPid + "/stat")), BUFFER_SIZE);
            String pidCpuRate = pidReader.readLine();
            if (pidCpuRate == null) {
                pidCpuRate = "";
            }

            parse(cpuRate, pidCpuRate);

    }

继续来看是怎样通知的,都通知了谁

    private void notifyBlockEvent(final long endTime) {
        // 触发 onBlockEvent,在子线程中执行
        HandlerThreadFactory.getWriteLogThreadHandler().post(new Runnable() {
            @Override
            public void run() {
                mBlockListener.onBlockEvent(startTime, endTime, startThreadTime, endThreadTime);
            }
        });
    }


        @Override
        public void onBlockEvent(long realTimeStart, long realTimeEnd,long threadTimeStart, long threadTimeEnd) {


![img](https://img-blog.csdnimg.cn/img_convert/2a0bf00ae06b0501c9506a525df49090.png)
![img](https://img-blog.csdnimg.cn/img_convert/2a626a4cab043e0f9d57e284dd96b3aa.png)

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618636735)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618636735)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值