android blockcanary 原理,BlockCanary的实现原理和源码分析

简单使用

implementation 'com.github.markzhai:blockcanary-android:1.5.0'

实现BlockCanaryContext, 重写provideBlockThreshold()方法设置检测阈值(例如500毫秒)

重写stopWhenDebugging()设置Debug模式是否启动检测

stopWhenDebugging:

返回 fasle: Debug启动检测

返回 true: Debug不检测 (默认返回true)

在Application里面初始化

public class AppBlockCanaryContext extends BlockCanaryContext {

@Override

public int provideBlockThreshold() {

return 500; //设置阈值

}

@Override

public boolean stopWhenDebugging() {

return false;

}

}

public class MyApplication extends Application {

@Override

public void onCreate() {

super.onCreate();

BlockCanary.install(this,new AppBlockCanaryContext()).start();

}

}

这样就能简单使用BlockCanary了, 当主线程耗时超过我们设置的阈值,就认为发生了卡顿, 记录各种信息保存到配置目录下的文件, 并弹出消息栏通知

BlockCanary的原理是什么? 为什么可以检查到卡顿?

在Acitvity启动流程当中我们知道在ActivityThread的main()方法当中,会创建主线程的Looper并且调用Looper.loop()开启循环处理主线程的消息

public static void main(String[] args) {

...

Looper.prepareMainLooper();

...

Looper.loop();

throw new RuntimeException("Main thread loop unexpectedly exited");

}

//Looper.loop()方法

public static void loop() {

final Looper me = myLooper();

final MessageQueue queue = me.mQueue;

...

for (;;) {

Message msg = queue.next(); // might block

if (msg == null) {

return;

}

final Printer logging = me.mLogging;

if (logging != null) {

logging.println(">>>>> Dispatching to " + msg.target + " " +

msg.callback + ": " + msg.what);

}

...

try {

msg.target.dispatchMessage(msg);

...

} catch (Exception exception) {

throw exception;

}

if (logging != null) {

logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);

}

}

}

从Looper.loop()源码中发现:

处理消息前, 从Looper取出Printer对象,并且调用logging.println()方法

dispatchMessage(msg)消息后,再次调用logging.println()

那么只要能给Looper设置我们的Printer, 记录start和end的时间,就能知道每个消息的耗时了

刚刚好Looper就有这个API让我们设置:

public void setMessageLogging(@Nullable Printer printer) {

mLogging = printer;

}

到这里就可以知道BlockCanary大概实现的原理了

给Looper设置一个Printer

对比主线程处理消息的start和end时间

如果时间超过我们设置的阈值, 就认为是卡顿了,弹出提示并记录调用栈/CPU等等信息,用来分析

整体流程图:

e24d0c1ffbe6

整体流程图

源码分析

BlockCanary的使用很简单

1.调用install(), 记录配置参数

2.调用start(), 开启工作

BlockCanary.install()做了什么事?

public static BlockCanary install(Context context, BlockCanaryContext blockCanaryContext) {

//保存Context和参数配置

BlockCanaryContext.init(context, blockCanaryContext);

//控制是否显示桌面logo图标

setEnabled(context, DisplayActivity.class, BlockCanaryContext.get().displayNotification());

return get();

}

调用init()方法, 记录Application和BlockCanaryContext, 为后面的处理提供上下文Context和配置参数(例如: 卡顿阈值,是否显示通知 等等...)

调用setEnabled()方法, 判断桌面是否显示黄色的logo图标

调用get()方法, 创建BlockCanary的实例,并且创建BlockCanaryInternals实例, 赋值给mBlockCanaryCore属性, 用来处理后面的流程

BlockCanary.start()做了什么事?

public void start() {

if (!mMonitorStarted) {

mMonitorStarted = true;

Looper.getMainLooper().setMessageLogging(mBlockCanaryCore.monitor);

}

}

start()方法只做了一件事: 给Looper设置一个Printer

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

mBlockCanaryCore.monitor是BlockCanaryInternals的成员属性LooperMonitor

LooperMonitor

class LooperMonitor implements 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)来通知处理后面的流程

比如: 处理调用栈和CPU, 保存到本地或者上传服务器等等......

调用stopDump()停止获取调用栈以及CPU的任务

startDump采集的信息包括:

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

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

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

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

startDump()里面并不会一直采集调用栈和CPU信息

只会在我们设置的阈值 * 0.8 的时间到了, 如果消息还没处理完成, 则开始去采集调用栈和CPU信息, 并且里面设置了采集的最大数量

总结:

1.给Looper设置一个Printer

2.对比主线程处理消息的start和end时间

3.如果时间超过我们设置的阈值, 就认为是卡顿了,弹出提示并记录调用栈/CPU等等信息,用来分析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值