Android-BlockCanary 使用和原理介绍

802 阅读2分钟

这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战

blockCanary介绍

android里面的性能优化,最主要的问题就是UI线程的阻塞导致的,对于如何准确的计算UI的绘制所耗费的时间,是非常有必要的,blockCanary是基于这个需求出现的,同样的,也是基于LeakCanary,和LeakCanary有着显示页面和堆栈信息。

简单使用

添加依赖
dependencies {
   
    // debug使用
    debugCompile 'com.github.markzhai:blockcanary-android:1.5.0'
    // 正式环境用
    releaseCompile 'com.github.markzhai:blockcanary-no-op:1.5.0'
}
在Application中初始化
在Application的onCreate方法中添加下面代码
BlockCanary.install(this, BlockCanaryContext()).start()
 //也可以自定义BlockCanaryContext,继承BlockCanaryContext就号

原理

BlockCanary 是依据整个app的主线程只有一个loop,所有的消息都是在Looper.loop() 这个方法中分发的(只贴出关键代码)

public static void loop() {
        。。。
        final Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }
        。。。
        long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
        try {
            msg.target.dispatchMessage(msg);
            if (observer != null) {
                observer.messageDispatched(token, msg);
            }
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } catch (Exception exception) {
            if (observer != null) {
                observer.dispatchingThrewException(token, msg, exception);
            }
            throw exception;
        } finally {
            ThreadLocalWorkSource.restore(origWorkSource);
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        。。。

        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }

     。。。
    }
}

logging在每个message处理的前后被调用,如果主线程卡住了,则在dispatchMessage里卡住了。

核心流程图

image.png

小结

利用了主线程的消息队列处理机制,通过Looper.getMainLooper().sendMessageLogging(mainLooperPrinter),在mainLooperPrinter中判断start和end,来获取主线程dispatch该message的开始和结束时间,并判定该时间超过阈值(如2000毫秒)为主线程卡慢发生,并dump出各种信息,提供开发者分析性能瓶颈。

public void println(String x) {
    if (mStopWhenDebugging && Debug.isDebuggerConnected()) {
        return;
    }
    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();
    }
}

源码分析

BlockCanary.install(this, BlockCanaryContext()).start()

先进入install方法

public static BlockCanary install(Context context, BlockCanaryContext blockCanaryContext) {
    BlockCanaryContext.init(context, blockCanaryContext);
    setEnabled(context, DisplayActivity.class, BlockCanaryContext.get().displayNotification());
    return get();
}
  • 调用init()方法, 记录Application和BlockCanaryContext, 为后面的处理提供上下文Context和配置参数
  • 调用setEnabled()方法, 判断桌面是否显示黄色的logo图标
  • 调用get()方法, 创建BlockCanary的实例,并且创建BlockCanaryInternals实例, 赋值给mBlockCanaryCore属性, 用来处理后面的流程

进入BlockCanaryContext的init方法

static void init(Context context, BlockCanaryContext blockCanaryContext) {
    sApplicationContext = context;
    sInstance = blockCanaryContext;
}

只是赋值操作

查看start方法

public void start() {
    if (!mMonitorStarted) {
        mMonitorStarted = true;
    //给looper设置一个打印    Looper.getMainLooper().setMessageLogging(mBlockCanaryCore.monitor);
    }
}

start()方法给Looper设置一个Printer

那么当Looper处理消息的前后, 就会调用mBlockCanaryCore.monitor的println()方法。

mBlockCanaryCore.monitor -》是一个LooperMonitor对象

class LooperMonitor implements Printer {
}

LooperMonitor继承Printer接口,查看里面的打印方法

@Override
public void println(String x) {
    //如果StopWhenDebugging, 就不检测
    if (mStopWhenDebugging && Debug.isDebuggerConnected()) {
        return;
    }
    if (!mPrintingStarted) {
        mStartTimestamp = System.currentTimeMillis();
        mStartThreadTimestamp = SystemClock.currentThreadTimeMillis();
        mPrintingStarted = true;
        startDump();//在子线程中获取调用栈和CPU信息
    } else {
        final long endTime = System.currentTimeMillis();
        mPrintingStarted = false;
        if (isBlock(endTime)) {//判断是否超过设置的最大值
            notifyBlockEvent(endTime);
        }
        stopDump();//停止获取调用栈和CPU信息
    }
}
//判断是否超过最大值
private boolean isBlock(long endTime) {
    return endTime - mStartTimestamp > mBlockThresholdMillis;
}

LooperMonitor的println()就是最核心的地方。

  • Looper处理消息前, 获取当前时间并且保存, 调用startDump()启动一个任务定时去采集 调用栈/CPU 等等信息
  • Looper处理消息完成, 获取当前时间, 判断是否超过我们自定义的阈值isBlock(endTime)如果超过了, 就调用notifyBlockEvent(endTime)来通知处理后面的流程
  • 调用stopDump()停止获取调用栈以及CPU的任务

startDump采集的信息包括:

  • 基本信息:机型, CPU内核数, 进程名, 内存, 版本号 等等

  • 耗时信息:实际耗时, 主线程时钟耗时, 卡顿开始时间和结束时间

  • CPU信息:时间段内CPU是否忙, 时间段内的系统CPU/应用CPU占比, I/O占- - CPU使用率

  • 堆栈信息:发生卡顿前的最近堆栈