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

news2024/12/26 23:15:52

什么是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 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);
        }

        ...
    }
}

注意到两个很关键的地方是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即可开启卡顿检测。

为了帮助到大家更好的全面清晰的掌握好性能优化,准备了相关的核心笔记(还该底层逻辑):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.coloradmin.cn/o/918636.html

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

相关文章

PDF校对工具正式上线,为用户提供卓越的文档校对解决方案

为满足当下对数字化文档校对的精准需求&#xff0c;我们今日正式发布全新的PDF校对工具。经过深入的技术研发与细致的测试&#xff0c;该工具旨在为企业和个人用户带来一个高效且准确的PDF文档校对平台。 PDF校对工具的主要特性&#xff1a; 1.全面性校对&#xff1a;工具支持…

尚硅谷大数据项目《在线教育之离线数仓》笔记003

视频地址&#xff1a;尚硅谷大数据项目《在线教育之离线数仓》_哔哩哔哩_bilibili 目录 第8章 数仓开发之DIM层 P039 P040 P041 P042 P043 P044 P045 P046 P047 P048 第8章 数仓开发之DIM层 P039 第8章 数仓开发之DIM层 DIM层设计要点&#xff1a; &#xff08;1&a…

项目计划怎么写? 6 个步骤助你万无一失

项目计划是项目管理的关键步骤&#xff0c;如果跳过这一步&#xff0c;项目还未启动就已岌岌可危。 什么是项目计划&#xff1f; 项目计划是实现特定目标或成果的详细路线图。它是一份全面的文件&#xff0c;回答了 “谁”、“什么”、“为什么”、"如何 "和 "…

美容行业如何快速搭建自己的预约小程序?

现在&#xff0c;搭建一个专属于美容行业的预约小程序不再是只有程序员才能做到的事情了。有了一些小程序制作平台的存在&#xff0c;任何人都可以轻松地制作出自己的小程序。下面&#xff0c;我将揭秘一个快速搭建专属美容行业预约小程序的秘诀。 首先&#xff0c;登录小程序制…

wazuh--sql检测

官网&#xff1a;Virtual Machine (OVA) - Installation alternatives Wazuh(Wazuh The Open Source Security Platform)&#xff1a;是一整套基于ossec安全检测工具和EFK日志工具构成的终端安全管理工具。不管是将其分类至HIDS&#xff0c;还是EDR&#xff0c;它都是一套通过…

【Linux】线程篇Ⅱ:

线程Ⅱ &#x1f517;接上篇【线程篇Ⅰ】五、线程库 和 线程 id六、同步与互斥 &#x1f517;接上篇【线程篇Ⅰ】 &#x1f449;【Linux】线程篇Ⅰ&#xff1a;线程和task_struct 执行流的理解、相关接口命令、线程异常、线程的私有和共享 五、线程库 和 线程 id 对于 Linux …

数藏平台遭到攻击怎么办?

数藏平台科技抢单以及DDoS攻击事件频发&#xff0c;造成服务器CPU耗尽&#xff0c;平台卡顿进不去&#xff0c;活动无法正常进行&#xff0c;攻击者入侵平台方数据库&#xff0c;恶意篡改账户余额&#xff0c;导致大量用户高价挂单仍可成交&#xff0c;最终导致数据异常&#x…

iFluor 594 Styramide是一种荧光染料,常用于生物分子标记和成像

试剂 | 基础知识概述&#xff08;部分&#xff09;: 中文名称&#xff1a;Alexa Fluor 594酪Styramide 分子量&#xff1a;1341.71 胺的优异替代品 100 Slides 英文名称&#xff1a;iFluor 594 Ex (nm)&#xff1a;588 Em (nm)&#xff1a;604 规格标准&#xff1a;1g&am…

docker快速上手运行js代码(dockerfile、镜像、容器、docker desktop图形化界面的基本使用、dockercompose)

1、导入&#xff1a; 1.1&#xff1a;准备js文件 我们本地有一个文件夹"docker_learn"&#xff0c;里面有一个index.js的文件&#xff0c;文件内只有一行代码 console.log("快速上手docker"); 1.2&#xff1a;运行js代码 我们使用vscode等ide打开这个…

pyqt5 多线程QThread自学记录

pyqt5多线程QThread自学记录 在编写qt的时候不加入多线程&#xff0c;运行程序往往会导致主进程卡死&#xff0c;比如执行下载某视频或者其他执行比较耗时的程序&#xff0c;泛滥了博客&#xff0c;就是没有理解怎么进行槽函数的关联和传递的 经过长时间的攻克&#xff0c;终于…

【附安装包】Dynaform 5.9.4安装教程

DYNAFORM是一款用于板料成形数值模拟的专用软件&#xff0c;是LS-DYNA求解器与ETA/FEMB前后处理器的完美结合&#xff0c;包含BSE、DFE、Formability三个大模块&#xff0c;几乎涵盖冲压模模面设计的所有要素&#xff0c;包括&#xff1a;定最佳冲压方向、坯料的设计、工艺补充…

界面设计软件都有哪些?推荐这7款

本文总结了7种“知名”软件界面设计工具&#xff0c;建议您尝试各种选择&#xff0c;以找到最适合您的UI设计工具。 对于UI设计师来说&#xff0c;应用程序的界面设计和制作是最常见的。面对设计师的需求&#xff0c;市场上出现了各种各样的软件界面设计工具&#xff0c;百花齐…

国标GB28181视频平台EasyGBS国标平台无法正常启动的问题解决方案

EasyGBS国标视频云服务是基于国标GB/T28181协议的视频能力平台&#xff0c;可实现的视频功能包括&#xff1a;实时监控直播、录像、检索与回看、语音对讲、云存储、告警、平台级联等功能。平台部署简单、可拓展性强&#xff0c;支持将接入的视频流进行全终端、全平台分发&#…

4-4 Representing text Exercise

本文所用资料下载 一. Representing text Let’s load Jane Austen’s Pride and Prejudice. We first split our text into a list of lines and pick an arbitrary line to focus on: with open(D:jane-austen/1342-0.txt, encodingutf8) as f:text f.read() lines text.…

UE4与pycharm联合仿真的调试问题及一些仿真经验

文章目录 ue4与pycharm联合仿真的调试问题前言ue4端的debug过程pycharm端 一些仿真经验小结 ue4与pycharm联合仿真的调试问题 前言 因为在实验中我需要用到py代码输出控制信息给到ue4中&#xff0c;并且希望看到py端和ue端分别在运行过程中的输出以及debug调试。所以&#xf…

uniapp打包ios

转载&#xff1a;如何将应用程序发布到 App Store_憧憬blog的博客-CSDN博客 憧憬blog主页 在强者的眼中&#xff0c;没有最好&#xff0c;只有更好。我们是移动开发领域的优质创作者&#xff0c;同时也是阿里云专家博主。 ✨ 关注我们的主页&#xff0c;探索iOS开发的无限可能&…

Mysql数据库迁移到达梦DM

因需求&#xff0c;原本使用的是mysql数据库&#xff0c;现要改为达梦数据库&#xff0c;顾记录迁移过程。 目录 一、下载DM连接工具https://www.dameng.com 二、迁移数据 三、迁移遇到的问题 一、下载DM连接工具https://www.dameng.com 下载安装好&#xff0c;会有如下图工…

8.23作业

设计一个Per类&#xff0c;类中包含私有成员&#xff1a;姓名、年龄、指针成员身高、体重&#xff0c;再设计一个Stu类&#xff0c;类中包含私有成员&#xff1a;成绩、Per类对象 p1&#xff0c;设计这两个类的构造函数、析构函数和拷贝构造函数。 #include <iostream>u…

Ansible学习笔记(二)

3.ansible的使用示例&#xff08;playbook&#xff09; 1.创建mysql 账户和mysql 组的 playbook ---#create mysql user and group - hosts: allremote_user: roottasks:- name: create groupgroup: namemysql systemyes gid306- name: create useruser: namemysql systemyes…

学习左耳听风栏目90天——第八天 8/90(学习左耳朵耗子的工匠精神,对技术的热爱)【答疑解惑:渴望、热情和选择】

答疑解惑&#xff1a;渴望、热情和选择 如何抽出时间学习&#xff1f;1. 要热爱学习&#xff0c;渴望学习&#xff0c;从学习中获取快乐 如何抽出时间学习&#xff1f; 1. 要热爱学习&#xff0c;渴望学习&#xff0c;从学习中获取快乐