Android开发之——怎么检测UI卡顿?(线上及线下)(3)

本文介绍了BlockCanary如何通过替换系统Printer收集主线程消息处理时间,判断卡顿,并提供详细堆栈信息、CPU使用情况。同时,文章还探讨了WatchDog原理在卡顿检测中的应用。作者强调了系统监控和性能优化对Android开发者的重要性。
摘要由CSDN通过智能技术生成

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);

}

}

}

注意到两个很关键的地方是logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what);logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);这两行代码,它调用的时机正好在dispatchMessage(msg)的前后,而主线程卡也就是在dispatchMessage(msg)卡住了。

BlockCanary的流程图


(图片来自网络)

blockcanary_flow.png

BlockCanary就是通过替换系统的Printer来增加了一些我们想要的堆栈信息,从而满足我们的需求。

替换原有的Printer是通过以下方法:

Looper.getMainLooper().setMessageLogging(mainLooperPrinter);

并在mainLooperPrinter中判断start和end,来获取主线程dispatch该message的开始和结束时间,并判定该时间超过阈值(如2000毫秒)为主线程卡慢发生,并dump出各种信息,提供开发者分析性能瓶颈。如下所示:

@Override

public void println(String x) {

if (!mStartedPrinting) {

mStartTimeMillis = System.currentTimeMillis();

mStartThreadTimeMillis = SystemClock.currentThreadTimeMillis();

mStartedPrinting = true;

startDump();

} else {

final long endTime = System.currentTimeMillis();

mStartedPrinting = false;

if (isBlock(endTime)) {

notifyBlockEvent(endTime);

}

stopDump();

}

}

private boolean isBlock(long endTime) {

return endTime - mStartTimeMillis > mBlockThresholdMillis;

}

  • BlockCanary dump的信息包括如下:

基本信息:安装包标示、机型、api等级、uid、CPU内核数、进程名、内存、版本号等

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

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

堆栈信息:发生卡慢前的最近堆栈,可以用来帮助定位卡慢发生的地方和重现路径

  • 获取系统状态信息是通过如下代码实现:

threadStackSampler = new ThreadStackSampler(Looper.getMainLooper().getThread(),

sBlockCanaryContext.getConfigDumpIntervalMillis());

cpuSampler = new CpuSampler(sBlockCanaryContext.getConfigDumpIntervalMillis());

下面看一下ThreadStackSampler是怎么工作的?

protected void doSample() {

// Log.d(“BlockCanary”, “sample thread stack: [” + mThreadStackEntries.size() + ", " + mMaxEntryCount + “]”);

StringBuilder stringBuilder = new StringBuilder();

// Fetch thread stack info

for (StackTraceElement stackTraceElement : mThread.getStackTrace()) {

stringBuilder.append(stackTraceElement.toString())

.append(Block.SEPARATOR);

}

// Eliminate obsolete entry

synchronized (mThreadStackEntries) {

if (mThreadStackEntries.size() == mMaxEntryCount && mMaxEntryCount > 0) {

mThreadStackEntries.remove(mThreadStackEntries.keySet().iterator().next());

}

mThreadStackEntries.put(System.currentTimeMillis(), stringBuilder.toString());

}

}

直接去拿主线程的栈信息, 每半秒去拿一次, 记录下来, 如果发生卡顿就显之显示出来 拿CPU的信息较麻烦, 从/proc/stat下面拿实时的CPU状态, 再从/proc/" + mPid + "/stat中读取进程时间, 再计算各CPU时间占比和CPU的工作状态.

基于系统WatchDog原理来实现


  • 启动一个卡顿检测线程,该线程定期的向UI线程发送一条延迟消息,执行一个标志位加1的操作,如果规定时间内,标志位没有变化,则表示产生了卡顿。如果发生了变化,则代表没有长时间卡顿,我们重新执行延迟消息即可。

public class WatchDog {

private final static String TAG = “budaye”;

//一个标志

private static final int TICK_INIT_VALUE = 0;

private volatile int mTick = TICK_INIT_VALUE;

//任务执行间隔

public final int DELAY_TIME = 4000;

//UI线程Handler对象

private Handler mHandler = new Handler(Looper.getMainLooper());

//性能监控线程

private HandlerThread mWatchDogThread = new HandlerThread(“WatchDogThread”);

//性能监控线程Handler对象

private Handler mWatchDogHandler;

//定期执行的任务

private Runnable mDogRunnable = new Runnable() {

@Override

public void run() {

if (null == mHandler) {

Log.e(TAG, “handler is null”);

return;

}

mHandler.post(new Runnable() {

@Override

public void run() {//UI线程中执行

mTick++;

}

});

try {

//线程休眠时间为检测任务的时间间隔

Thread.sleep(DELAY_TIME);

} catch (InterruptedException e) {

e.printStackTrace();

}

//当mTick没有自增时,表示产生了卡顿,这时打印UI线程的堆栈

if (TICK_INIT_VALUE == mTick) {

StringBuilder sb = new StringBuilder();

//打印堆栈信息

StackTraceElement[] stackTrace = Looper.getMainLooper().getThread().getStackTrace();

for (StackTraceElement s : stackTrace) {

sb.append(s.toString() + “\n”);

}

Log.d(TAG, sb.toString());

} else {

mTick = TICK_INIT_VALUE;

}

mWatchDogHandler.postDelayed(mDogRunnable, DELAY_TIME);

}

};

/**

  • 卡顿监控工作start方法

*/

public void startWork(){

mWatchDogThread.start();

mWatchDogHandler = new Handler(mWatchDogThread.getLooper());

mWatchDogHandler.postDelayed(mDogRunnable, DELAY_TIME);

}

}

  • 调用startWork即可开启卡顿检测。

最后


小编在网上收集了一些 Android 开发相关的学习文档、面试题、Android 核心笔记等等文档,希望能帮助到大家学习提升,如有需要参考的可以直接去我 CodeChina地址:https://codechina.csdn.net/u012165769/Android-T3 访问查阅。

尾声

最后,我再重复一次,如果你想成为一个优秀的 Android 开发人员,请集中精力,对基础和重要的事情做深度研究。

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

这里,笔者分享一份从架构哲学的层面来剖析的视频及资料分享给大家梳理了多年的架构经验,筹备近6个月最新录制的,相信这份视频能给你带来不一样的启发、收获。

Android进阶学习资料库

一共十个专题,包括了Android进阶所有学习资料,Android进阶视频,Flutter,java基础,kotlin,NDK模块,计算机网络,数据结构与算法,微信小程序,面试题解析,framework源码!

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
学的层面来剖析的视频及资料分享给大家梳理了多年的架构经验,筹备近6个月最新录制的,相信这份视频能给你带来不一样的启发、收获。

[外链图片转存中…(img-a2hsZpwz-1714732578137)]

Android进阶学习资料库

一共十个专题,包括了Android进阶所有学习资料,Android进阶视频,Flutter,java基础,kotlin,NDK模块,计算机网络,数据结构与算法,微信小程序,面试题解析,framework源码!
[外链图片转存中…(img-4GPMkAmG-1714732578138)]
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

  • 12
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值