怎么检测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的数据阵列&…

Adapting Language Models to Compress Contexts

本文是LLM系列文章&#xff0c;针对《Adapting Language Models to Compress Contexts》的翻译。 使语言模型适应上下文压缩 摘要1 引言2 相关工作3 方法4 实验5 上下文学习6 压缩检索语料库实现高效推理7 结论不足 摘要 1 引言 2 相关工作 3 方法 4 实验 5 上下文学习 …

亚马逊云科技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;并提供一个案例…

编解码视频测试序列集

https://kodi.wiki/view/Samples https://ultravideo.fi/#testsequences 原文&#xff1a;http://forum.doom9.org/archive/index.php/t-135034.html his thread is meant to hold links and references to test video clip sequences intended to be used for various video…

Linux 网络文件共享介绍

Linux 网络文件共享介绍 一.常见的存储类型 目前常见的存储类型有 DAS,NAS,SAN 等&#xff0c;最主要的区别是硬盘存储媒介是如何 于处理器连接的&#xff0c;以及处理器使用何种方式来访问磁盘&#xff0c;以及访问磁盘使用 的协议(网络协议、I/O 协议)。 三种存储类型如下 直…

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;并见证了项目的正式启动。 无锡华光座椅弹簧有限公…

Angular中如何获取URL参数?

Angular中的ActivatedRoute中保存着路由信息&#xff0c;可用来提取URL中的路由参数。 constructor(private route: ActivatedRoute){}ngOnInit(): void {this.getUser();}getUser(): void {const id this.route.snapshot.paramMap.get(id);} }route.snapshot是一个路由信息的…

计算机网络面试题

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

Java开发工作问题整理与记录

1、为什么Autowired不能注入static成员属性 扫描Class类需要注入的元数据的时候&#xff0c;直接选择忽略掉了static成员&#xff08;包括属性和方法&#xff09; Spring 依赖注入是依赖set方法, set方法是普通的对象方法,static变量是类的属性 AutowiredAnnotationBeanPostP…

嵌入式开发之堆栈调试打印

简介 打印堆栈的常用方法包括&#xff1a; glibc中的backtrace函数gcc内置函数__builtin_return_address第三方库libunwind 1 glibc中的backtrace 1. 1函数原型 #include <execinfo.h>/** 功能: 获取当前线程的调用堆栈并存放在buffer中(指向字符串数组的指针)* par…

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

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

kafka复习:(20):消费者拦截器的使用

一、定义消费者拦截器&#xff08;只消费含"sister"的消息&#xff09; package com.cisdi.dsp.modules.metaAnalysis.rest;import org.apache.kafka.clients.consumer.ConsumerInterceptor; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.…

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{ …