android blockcanary 原理,blockCanary原理
blockCanary
对于android里面的性能优化,最主要的问题就是UI线程的阻塞导致的,对于如何准确的计算UI的绘制所耗费的时间,是非常有必要的,blockCanary是基于这个需求出现的,同样的,也是基于LeakCanary,和LeakCanary有着显示页面和堆栈信息。
使用
首先在gradle引入
implementation 'com.github.markzhai:blockcanary-android:1.5.0'
然后Application里面进行初始化和start
BlockCanary.install(this, new BlockCanaryContext()).start();
原理:
其中BlockCanaryContext表示的就是我们监测的某些参数,包括卡顿的阈值、输出文件的路径等等
//默认卡顿阈值为1000ms
public int provideBlockThreshold() {
return 1000;
}
//输出的log
public String providePath() {
return "/blockcanary/";
}
//支持文件上传
public void upload(File zippedFile) {
throw new UnsupportedOperationException();
}
//可以在卡顿提供自定义操作
@Override
public void onBlock(Context context, BlockInfo blockInfo) {
}
其中,init只是创建出BlockCanary实例。主要是start方法的操作。
/**
* Start monitoring.
*/
public void start() {
if (!mMonitorStarted) {
mMonitorStarted = true;
Looper.getMainLooper().setMessageLogging(mBlockCanaryCore.monitor);
}
}
其实就是给主线程的Looper设置一个monitor。
我们可以先看看主线程的looper实现。
public static void loop() {
...
for (;;) {
...
// This must be in a local variable, in case a UI event sets the logger
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);
}
...
}
}
在上面的loop循环的代码中,msg.target.dispatchMessage就是我们UI线程收到每一个消息需要执行的操作,都在其内部执行。
系统也在其执行的前后都会执行logging类的print的方法,这个方法是我们可以自定义的。所以只要我们在运行的前后都添加一个时间戳,用运行后的时间减去运行前的时间,一旦这个时间超过了我们设定的阈值,那么就可以说这个操作卡顿,阻塞了UI线程,最后通过dump出此时的各种信息,来分析各种性能瓶颈。
那么接下来可以看看这个monitor的println方法。
@Override
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();
}
}
在ui操作执行前,将会记录当前的时间戳,同时会startDump。
在ui操作执行后,将会计算当前是否卡顿了,如果卡顿了,将会回调到onBlock的onBlock方法。同时将会停止dump。
为什么操作之前就开启了startDump,而操作执行之后就stopDump呢?
private void startDump() {
if (null != BlockCanaryInternals.getInstance().stackSampler) {
BlockCanaryInternals.getInstance().stackSampler.start();
}
if (null != BlockCanaryInternals.getInstance().cpuSampler) {
BlockCanaryInternals.getInstance().cpuSampler.start();
}
}
public void start() {
if (mShouldSample.get()) {
return;
}
mShouldSample.set(true);
HandlerThreadFactory.getTimerThreadHandler().removeCallbacks(mRunnable);
HandlerThreadFactory.getTimerThreadHandler().postDelayed(mRunnable,
BlockCanaryInternals.getInstance().getSampleDelay());
}
其实startDump的时候并没有马上start,而是会postDelay一个runnable,这个runnable就是执行dump的真正的操作,delay的时间就是我们设置的阈值的0.8
也就是,一旦我们的stop在设置的延迟时间之前执行,就不会真正的执行dump操作。
public void stop() {
if (!mShouldSample.get()) {
return;
}
mShouldSample.set(false);
HandlerThreadFactory.getTimerThreadHandler().removeCallbacks(mRunnable);
}
只有当stop操作在设置的延迟时间之后执行,才会执行dump操作。
private Runnable mRunnable = new Runnable() {
@Override
public void run() {
doSample();
if (mShouldSample.get()) {
HandlerThreadFactory.getTimerThreadHandler()
.postDelayed(mRunnable, mSampleInterval);
}
}
};
这个doSameple分别会dump出stack信息和cpu信息。
cpu:
try {
cpuReader = new BufferedReader(new InputStreamReader(
new FileInputStream("/proc/stat")), BUFFER_SIZE);
String cpuRate = cpuReader.readLine();
if (cpuRate == null) {
cpuRate = "";
}
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);
}
stack:
protected void doSample() {
StringBuilder stringBuilder = new StringBuilder();
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());
}
sStackMap.put(System.currentTimeMillis(), stringBuilder.toString());
}
}
这样,整个blockCanary的执行过程就完毕了。
ANR
当卡顿时间大于一定值之后,将会造成ANR,那么Android系统的ANR是如何检测出来的呢?其实就是通过Watchdog来实现的,这个Watchdog是一个线程。
public class Watchdog extends Thread {
}
我们主要看一下其中的run方法的实现。
@Override
public void run() {
boolean waitedHalf = false;
while (true) {
final ArrayListblockedCheckers;
final String subject;
final boolean allowRestart;
int debuggerWasConnected = 0;
synchronized (this) {
long timeout = CHECK_INTERVAL;
// Make sure we (re)spin the checkers that have become idle within
// this wait-and-check interval
for (int i=0; i0) {
debuggerWasConnected--;
}
// NOTE: We use uptimeMillis() here because we do not want to increment the time we
// wait while asleep. If the device is asleep then the thing that we are waiting
// to timeout on is asleep as well and won't have a chance to run, causing a false
// positive on when to kill things.
long start = SystemClock.uptimeMillis();
while (timeout > 0) {
if (Debug.isDebuggerConnected()) {
debuggerWasConnected = 2;
}
try {
wait(timeout);
} catch (InterruptedException e) {
Log.wtf(TAG, e);
}
if (Debug.isDebuggerConnected()) {
debuggerWasConnected = 2;
}
timeout = CHECK_INTERVAL - (SystemClock.uptimeMillis() - start);
}
在这个run方法中,会开启一个死循环,主要用于持续检测ANR
while (true) {
}
通过wait,设置每一次休眠时间,
long start = SystemClock.uptimeMillis();
while (timeout > 0) {
if (Debug.isDebuggerConnected()) {
debuggerWasConnected = 2;
}
try {
wait(timeout);
} catch (InterruptedException e) {
Log.wtf(TAG, e);
}
if (Debug.isDebuggerConnected()) {
debuggerWasConnected = 2;
}
timeout = CHECK_INTERVAL - (SystemClock.uptimeMillis() - start);
}
当timeout计算完毕之后,会尝试获取当前各个的线程的状态
final int waitState = evaluateCheckerCompletionLocked();
private int evaluateCheckerCompletionLocked() {
int state = COMPLETED;
for (int i=0; i
public int getCompletionStateLocked() {
if (mCompleted) {
return COMPLETED;
} else {
long latency = SystemClock.uptimeMillis() - mStartTime;
if (latency < mWaitMax/2) {
return WAITING;
} else if (latency < mWaitMax) {
return WAITED_HALF;
}
}
return OVERDUE;
}
一旦有线程等待时间超过了最大等待时间,则表示当前已经有ANR。需要dump此时的堆栈信息。
if (waitState == COMPLETED) {
// The monitors have returned; reset
waitedHalf = false;
continue;
} else if (waitState == WAITING) {
// still waiting but within their configured intervals; back off and recheck
continue;
} else if (waitState == WAITED_HALF) {
if (!waitedHalf) {
// We've waited half the deadlock-detection interval. Pull a stack
// trace and wait another half.
ArrayListpids = new ArrayList();
pids.add(Process.myPid());
ActivityManagerService.dumpStackTraces(true, pids, null, null,
getInterestingNativePids());
waitedHalf = true;
}
continue;
}
此外,还有一个第三方库,ANRWatchDog,也是用来检测Anr的,其实原理更加简单,
public void run() {
setName("|ANR-WatchDog|");
int lastTick;
int lastIgnored = -1;
while (!isInterrupted()) {
lastTick = _tick;
_uiHandler.post(_ticker);
try {
Thread.sleep(_timeoutInterval);
}
catch (InterruptedException e) {
_interruptionListener.onInterrupted(e);
return ;
}
// If the main thread has not handled _ticker, it is blocked. ANR.
if (_tick == lastTick) {
if (!_ignoreDebugger && Debug.isDebuggerConnected()) {
if (_tick != lastIgnored)
Log.w("ANRWatchdog", "An ANR was detected but ignored because the debugger is connected (you can prevent this with setIgnoreDebugger(true))");
lastIgnored = _tick;
continue ;
}
ANRError error;
if (_namePrefix != null)
error = ANRError.New(_namePrefix, _logThreadsWithoutStackTrace);
else
error = ANRError.NewMainOnly();
_anrListener.onAppNotResponding(error);
return;
}
}
}
它会在线程中,利用uiHanlder抛出一个计数器,然后wait指定时间,一旦等待时间到达,那么它会检查计数的值是否发生改变,如果没有发生改变,表示uiHandler的计算方法并没有执行到。也就是出现了Anr,此时需要dump堆栈信息。
android blockcanary 原理,blockCanary原理相关推荐
- android动画的实现原理,Android动画的实现原理 .
1.动画运行模式 独行模式 中断模式 2.Animation类 每个动画都重载了父类的applyTransformation方法这个方法的主要作用是把一些属性组装成一个Transformation类, ...
- android 版本更新原理,Android系统Recovery工作原理之使用update.zip升级过程分析(二)...
Android系统Recovery工作原理之使用update.zip升级过程分析(二)---update.zip差分包问题的解决 在上一篇末尾提到的生成差分包时出现的问题,现已解决,由于最近比较忙,相 ...
- Android Handler与Looper原理简析
一直感觉自己简直就是一个弱智,最近越来越感觉是这样了,真的希望自己有一天能够认同自己,认同自己. 本文转载于:https://juejin.im/post/59083d7fda2f60005d14ef ...
- Android系统Recovery工作原理之使用update.zip升级过程分析(五)
Android系统Recovery工作原理之使用update.zip升级过程分析(五)---update.zip包从上层进入Recovery服务文章开头我们就提到update.zip包来源有两种,一个 ...
- 【Android架构师java原理详解】二;反射原理及动态代理模式
前言: 本篇为Android架构师java原理专题二:反射原理及动态代理模式 大公司面试都要求我们有扎实的Java语言基础.而很多Android开发朋友这一块并不是很熟练,甚至半路初级底子很薄,这给我 ...
- 深入解析阿里Android热修复技术原理
前言:本文框架 什么是热修复? 热修复框架分类 技术原理及特点 Tinker框架解析 各框架对比图 总结 通过阅读本文,你会对热修复技术有更深的认知,本文会列出各类框架的优缺点以及技术原理,文章末尾简 ...
- Android热修复技术原理详解(最新最全版本)
本文框架 什么是热修复? 热修复框架分类 技术原理及特点 Tinker框架解析 各框架对比图 总结 通过阅读本文,你会对热修复技术有更深的认知,本文会列出各类框架的优缺点以及技术原理,文章末尾简单 ...
- android长截屏代码,android长截屏原理及实现代码
android长截屏原理及实现代码 发布时间:2020-08-31 06:55:16 来源:脚本之家 阅读:158 作者:Android笔记 小米系统自带的长截屏应该很多人都用过,效果不错.当长截屏时 ...
- android digest 认证,探究 Android 签名机制和原理
背景 最近在调研一个测试工具的使用,在使用中发现被测试工具处理过的apk文件经安装后打开就会崩溃,分析崩溃日志后原因是签名不一致导致的. 说到Android中的签名,可能大家都知道签名的目的就是为了保 ...
- Android系统Recovery工作原理之使用update.zip升级过程分析(二)---u...
2019独角兽企业重金招聘Python工程师标准>>> Android系统Recovery工作原理之使用update.zip升级过程分析(二)---update.zip差分包问题的 ...
最新文章
- 模拟退火 HDU - 2899 Strange Function
- windows-台式机添加硬盘后如何加载、格式化新的分区
- WebKit与event.layerX和event.layerY有关的问题
- How Spring Boot Autoconfiguration Magic Works--转
- 博主新书:《大数据日知录:架构与算法》目录
- Codeforces Round #671 (Div. 2)
- mysql除法运算保留小数的用法
- altas(ajax)控件(二):悬浮面板控件AlwaysVisibleControl
- 【数据库系统设计】DBMS的数据库保护
- Python 定义源码编码 (Source Encoding)
- Find命令使用详解及实例分析
- firefly 环境配置所需工具
- 最近给公司写的it规范,有经验的朋友给点意见
- 如何评价单片机大神郭天祥?
- 移动终端基带芯片的基本架构介绍之一(arm框架的软硬件组合)
- POJ - 3537 Crosses and Crosses (MLI - SG)
- tekton TriggerBinding资源
- 数据库 | ATACdb:一个全面的人类染色质可开放性数据库
- 小菊的语义分割2——数据集的制作(一): ISPRS_Potsdam遥感图像数据集
- 黑龙江计算机专业好的大学排名,黑龙江计算机专业比较好的大学
热门文章
- C++什么时候需要使用“常引用”?
- 经典C语言程序100例之六六
- php查询数据存到下一界面_PHP从另一个页面获取数据
- php计算200以内偶数的和并输出_如何用PHP实现数组中偶数位置元素大于奇数位置元素?...
- 04_Nginx命令行参数,控制信号,Nginx启动、停止、重启命令
- SQLite 简介(http://www.w3cschool.cc/sqlite/sqlite-intro.html)
- Keras.layers各种层介绍,网络层、卷积层、池化层 等
- 等于x分之a的平方的导数_清华学霸丨手把手教你导数大题如何骗分(文理通用),家长为孩子收...
- html5时间画布走动,javascript+HTML5 canvas绘制时钟功能示例
- 力扣:11盛水最多的容器