怎么检测UI卡顿?(线上及线下)

什么是UI卡顿?

在Android系统中,我们知道UI线程负责我们所有视图的布局,渲染工作,UI在更新期间,如果UI线程的执行时间超过16ms,则会产生丢帧的现象,而大量的丢帧就会造成卡顿,影响用户体验。

UI卡顿产生的原因?

  • 在UI线程中做了大量的耗时操作,导致了UI刷新工作的阻塞。
  • 系统CPU资源紧张,APP所能分配的时间片减少。
  • Ardroid虚拟机频繁的执行GC操作,导致占用了大量的系统资源,同时也会导致UI线程的短暂停顿,从而产生卡顿。
  • 代码编写不当,产生了过度绘制,导致CPU执行时间变长,早场卡顿。

从上可知,大部分的卡顿原因都产生于代码编写不当导致,而这类问题都可以通过各种优化方案进行优化,所以我们需要做的就是尽可能准确的找到卡顿的原因,定位到准确的代码模块,最好是能定位到哪个方法导致卡顿,这样我们APP的性能就能得到很大的提升。

UI卡顿方案

  • 开发阶段

在开发阶段我们可以借助开发工具为我们提供的各种便利来有效的识别卡顿,如下:

System Trace

具体使用可以看blog.csdn.net/u011578734/… 写的文章。

Android CPU Profiler

  • Android Studio CPU 性能剖析器可实时检查应用的 CPU 使用率和线程活动。你还可以检查方法跟踪记录、函数跟踪记录和系统跟踪记录中的详细信息。
  • 使用CPU profiler可以查看主线程中,每个方法的耗时情况,以及每个方法的调用栈,可以很方便的分析卡顿产生的原因,以及定位到具体的代码方法。

具体使用方法可以参考 blog.csdn.net/u011578734/…

线上UI卡顿检测方案

线上检测方案比较流行的是BlockCanary和WatchDog,下面我们就看看它们是怎么做到检测UI卡顿的并反馈给开发人员。

BlockCanary

  • BlockCanary能检测到主线程的卡顿, 并将结果记录下来, 以友好的方式展示,很类似于LeakCanary的展示。

BlockCanary的使用很简单,只要在Application中进行设置一下就可以如下:

BlockCanary.install(this, new AppBlockCanaryContext()).start();
  • AppBlockCanaryContext继承自BlockCanaryContext是对BlockCanary中各个参数进行配置的类

可配置参数如下:

//卡顿阀值
int getConfigBlockThreshold();
boolean isNeedDisplay();
String getQualifier();
String getUid();
String getNetworkType();
Context getContext();
String getLogPath();
boolean zipLogFile(File[] src, File dest);
//可将卡顿日志上传到自己的服务
void uploadLogFile(File zippedFile);
String getStackFoldPrefix();
int getConfigDumpIntervalMillis();
  • 在某个消息执行时间超过设定的标准时会弹出通知进行提醒,或者上传。

原理

熟悉Android的Handler机制的同学一定知道,Handler中重要的组成部分,looper,并且应用的主线程只有一个Looper存在,不管有多少handler,最后都会回到这里。 我们注意到Looper.loop()中有这么一段代码:

public static void loop() {...for (;;) {...// This must be in a local variable, in case a UI event sets the loggerPrinter 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);}...}
}

注意到两个很关键的地方是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 infofor (StackTraceElement stackTraceElement : mThread.getStackTrace()) {stringBuilder.append(stackTraceElement.toString()).append(Block.SEPARATOR);}// Eliminate obsolete entrysynchronized (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() {@Overridepublic void run() {if (null == mHandler) {Log.e(TAG, "handler is null");return;}mHandler.post(new Runnable() {@Overridepublic 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即可开启卡顿检测。

为了帮助到大家更好的全面清晰的掌握好性能优化,准备了相关的核心笔记(还该底层逻辑):https://qr18.cn/FVlo89

性能优化核心笔记:https://qr18.cn/FVlo89

启动优化

内存优化

UI优化

网络优化

Bitmap优化与图片压缩优化https://qr18.cn/FVlo89

多线程并发优化与数据传输效率优化

体积包优化

《Android 性能监控框架》:https://qr18.cn/FVlo89

《Android Framework学习手册》:https://qr18.cn/AQpN4J

  1. 开机Init 进程
  2. 开机启动 Zygote 进程
  3. 开机启动 SystemServer 进程
  4. Binder 驱动
  5. AMS 的启动过程
  6. PMS 的启动过程
  7. Launcher 的启动过程
  8. Android 四大组件
  9. Android 系统服务 - Input 事件的分发过程
  10. Android 底层渲染 - 屏幕刷新机制源码分析
  11. Android 源码分析实战

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/51845.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Matlab彩色图像转索引图像

索引图像 索引图像是一种把像素值直接作为RGB调色板下标的图像。索引图像包括一个数据矩阵X&#xff0c;一个调色板矩阵map&#xff0c;也称为颜色映像矩阵。其中&#xff0c;数据矩阵X可以是8位无符号整型、16位无符号整型或双精度类型。调色板矩阵map是一个m3的数据阵列&…

亚马逊云科技CEO谈及企业领导力原则的核心:坚持顾客至上

亚马逊云科技首席执行官Adam Selipsky几乎从一开始就在那里&#xff1a;他于2005年加入&#xff0c;在效力亚马逊11年后于2016年离开&#xff0c;转而经营Tableau&#xff0c;并于2021年成为亚马逊云科技首席执行官。当时亚马逊云科技前首席执行官安迪贾西(Andy Jassy)接替杰夫…

大数据(一)定义、特性

大数据&#xff08;一&#xff09;定义、特性 本文目录&#xff1a; 一、写在前面的话 二、大数据定义 三、大数据特性 3.1、大数据的大量 (Volume) 特性 3.2、大数据的高速(Velocity)特性 3.3、大数据的多样化 (Variety) 特性 3.4、大数据的价值 (value) 特性 3.5、大…

废品回收抢单派单小程序开源版开发

废品回收抢单派单小程序开源版开发 用户注册和登录&#xff1a;用户可以通过手机号码注册和登录小程序&#xff0c;以便使用废品回收抢单派单功能。废品回收订单发布&#xff1a;用户可以发布废品回收订单&#xff0c;包括废品种类、数量、回收地点等信息。废品回收抢单&#…

React通过docx-preview预览Word文档

前言 在基于React的Web应用中&#xff0c;我们经常遇到需要预览和展示Word文档的需求。而docx-preview是一个优秀的React组件库&#xff0c;可以帮助我们实现在Web页面上预览Word文档的功能。本文将介绍如何使用docx-preview组件来实现Word文档的预览&#xff0c;并提供一个案例…

JavaScript箭头函数

Arrow Functions&#xff08;箭头函数&#xff09;是 ES6 中引入的一种新的函数表达式语法&#xff0c;它可以更简洁地定义函数&#xff0c;并且不需要像普通函数一样使用 function 关键字。 例如我们上节课的代码&#xff1a; const peopleAge function calcAge1(birthYear)…

vue3+ts+uniapp小程序端自定义日期选择器基于内置组件picker-view + 扩展组件 Popup 实现自定义日期选择及其他选择

vue3ts 基于内置组件picker-view 扩展组件 Popup 实现自定义日期选择及其他选择 vue3tsuniapp小程序端自定义日期选择器 1.先上效果图2.代码展示2.1 组件2.2 公共方法处理日期2.3 使用组件 3.注意事项3.1refSelectDialog3.1 backgroundColor"#fff" 自我记录 1.先上…

error: can‘t find Rust compiler

操作系统 win11 pip install -r requirements.txt 报错如下 Using cached https://pypi.tuna.tsinghua.edu.cn/packages/56/fc/a3c13ded7b3057680c8ae95a9b6cc83e63657c38e0005c400a5d018a33a7/pyreadline3-3.4.1-py3-none-any.whl (95 kB) Building wheels for collected p…

哲讯科技携手无锡华启动SCM定制化项目,共谋数字化转型之路

无锡华光座椅弹簧有限公司启动SCM定制化项目 近日&#xff0c;无锡华光座椅弹簧有限公司顺利举行了SCM定制化项目的启动会。本次启动会作为该项目实施的重要里程碑&#xff0c;吸引了双方项目组核心成员的共同参与&#xff0c;并见证了项目的正式启动。 无锡华光座椅弹簧有限公…

计算机网络面试题

文章目录 描述HTTP和HTTPS的区别Cookie和Session有什么区别BIO、NIO、AIOTCP三次握手和四次挥手跨域请求是什么&#xff1f;有什么问题&#xff1f;怎么解决&#xff1f;网页输入url&#xff0c;到渲染整个界面的整个过程&#xff0c;以及中间件用了什么协议Rest、RestfulTCP的…

【C语言】动态内存管理(malloc,free,calloc,realloc)-- 详解

一、动态内存分配 定义&#xff1a;动态内存分配 (Dynamic Memory Allocation) 就是指在程序执行的过程中&#xff0c;动态地分配或者回收存储空间的分配内存的方法。动态内存分配不像数组等静态内存分配方法那样&#xff0c;需要预先分配存储空间&#xff0c;而是由系统根据程…

0103水平分片-jdbc-shardingsphere-中间件

文章目录 1 准备服务器1.1 创建server-order0容器1.2 创建server-order1容器 2、基本水平分片2.1、基本配置2.2、数据源配置2.3、标椎分片表配置2.4、行表达式2.5、分片算法配置2.6、分布式序列算法 3、多表关联3.1、创建关联表3.2、创建实体类3.3、创建Mapper3.4、配置关联表3…

【C++设计模式】用动画片《少年骇客》(Ben10)来解释策略模式

2023年8月25日&#xff0c;周五上午 今天上午学习设计模式中的策略模式时&#xff0c;发现这个有点像很多卡通片里面的变身器... #include<iostream>//alien hero是外星英雄的意思 //在《少年骇客》中&#xff0c;主角可以通过变身器变成10种外星英雄 class AlienHero{ …

手机盖板IR油墨透光率检测仪T03

手机盖板作为手机最外层玻璃面板&#xff0c;其加工一般有落料、倒边、抛光、镀膜、丝印等多道加工工序组成&#xff0c;其中任何一个工序出现差错&#xff0c;都有可能导致手机盖板产生缺陷&#xff0c;例如漏油、透光、IR孔不良、视窗划伤、油墨区划伤、內污、边花等&#xf…

CSS中如何实现元素之间的间距(Margin)合并效果?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 外边距合并的示例&#xff1a;⭐ 如何控制外边距合并&#xff1a;⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff…

C语言实现状态机

关于状态机&#xff0c;基础的知识点可以自行理解&#xff0c;讲解的很多&#xff0c;这里主要是想写一个有限状态机FSM通用的写法&#xff0c;目的在于更好理解&#xff0c;移植&#xff0c;节省代码阅读与调试时间&#xff0c;体现出编程之美。 传统的实现方案 if...else : …

Nginx入门——Nginx的docker版本和windows版本安装和使用 代理的概念 负载分配策略

目录 引出nginx是啥正向代理和反向代理正向代理反向代理 nginx的安装使用Docker版本的nginx安装下载创建挂载文件获取配置文件创建docker容器拷贝容器中的配置文件删除容器 创建运行容器开放端口进行代理和测试 Windows版本的使用反向代理多个端口运行日志查看启动关闭重启 负载…

【BASH】回顾与知识点梳理(三十八)

【BASH】回顾与知识点梳理 三十八 三十八. 源码概念及简单编译38.1 开放源码的软件安装与升级简介什么是开放源码、编译程序与可执行文件什么是函式库什么是 make 与 configure什么是 Tarball 的软件如何安装与升级软件 38.2 使用传统程序语言进行编译的简单范例单一程序&#…

leetcode304. 二维区域和检索 - 矩阵不可变(java)

前缀和数组 二维区域和检索 - 矩阵不可变题目描述前缀和代码演示 一维数组前缀和 二维区域和检索 - 矩阵不可变 难度 - 中等 原题链接 - 二维区域和检索 - 矩阵不可变 题目描述 给定一个二维矩阵 matrix&#xff0c;以下类型的多个请求&#xff1a; 计算其子矩形范围内元素的总…

python3对接godaddy API,实现自动更改域名解析(DDNS)

python3对接godaddy API&#xff0c;实现自动更改域名解析&#xff08;DDNS&#xff09; 文章开始前&#xff0c;先解释下如下问题&#xff1a; ①什么是域名解析&#xff1f; 域名解析一般是指通过一个域名指向IP地址&#xff08;A解析&#xff09;&#xff0c;然后我们访问…