Android性能优化(三)—— 绘制优化

news2024/9/25 17:13:34

运行的 Android 手机,虽然配置在不断的提升,但是仍然无法和 PC 相比,无法做到 PC 那样拥有超大内存以及高性能的 CPU。因此在开发 Android 应用程序时也不可能无限制的使用 CPU 和内存,如果对 CPU 和 内存使用不当也会造成应用的卡顿和内存溢出等问题。

1 绘制性能分析

Android 应用需要将自己的洁面展示给用户,用户会和洁面进行交互,界面流畅度至关重要。

1.1 绘制原理

View 的绘制流程有 3 个步骤,分别是 measurelayoutdraw,它们主要运行在系统的应用框架层,而真正将数据渲染到屏幕上的则是系统 Native 层的 SurfaceFlinger 服务来完成的。

绘制过程主要由 CPU 来进行 MeasureLayoutRecordExecute 的数据计算工作,GPU 负责栅格化、渲染。CPUGPU (图形处理器 graphics processing unit)是通过图形驱动层来进行连接的,图形驱动层维护了一个队列,CPUdisplay list 添加到该队列中,这样 GPU 就可以从这个队列中取出数据进行绘制。

说到绘制性能就需要提到帧这个概念。帧数就是在 1s 时间里传输的图片的量,也可以理解为图形处理器每秒钟能刷新几次,通过用 FPSFrames Per Second)表示。每一帧其实就是静止的图像,通过快速连续地显示帧便形成了运动的假象。最简单的举例就是我们在玩游戏的时候,如果画面在 60fps 则不会感到卡顿,如果低于 60fps ,比如 50fps 则会感到卡顿。 这是因为人类的大脑会不断的接收并处理眼球看到的信息,单位时间内越多的帧被处理,就越能有效地被大脑识别,大脑能感知的最小的帧数载 10fps ~ 12fps,这个时候大脑就分不清楚这个图像是静止的还是变化的。

要想画面保持在 60fps,需要屏幕在 1s 内刷新 60 次,也就是没 16.6667ms 刷新一次(绘制时长在 16ms 以内)。

Android 系统每隔 16ms 发出 VSYNC 信号,触发对 UI 进行渲染,如果每次渲染都成功,这样就能够达到流畅画面所需要的 60fps,那什么是 VSYNC 呢?VSYNC 就是 Vertical Synchronization(垂直同步)的缩写,是一种定时中断,一旦收到 VSYNC 信号,CPU 就开始处理各帧数据。如果某个操作要花费 24ms,这样系统在得到 VSYNC 信号时无法进行正常的渲染,会发生丢帧。用户会在 32ms 中看到同一帧的画面。

产生卡顿原因有很多,主要有以下几点:

  • 布局 Layout 过于复杂,无法在 16ms 内完成渲染;
  • 同一时间动画执行的次数过多,导致 CPUGPU 负载过重;
  • View 过度绘制,导致某些像素在同一帧时间内会被绘制多次;
  • UI 线程中做了稍微耗时的操作;
  • GC 回收时暂停时间过长或者频繁 GC 产生大量的暂停时间;

1.2 工具

1.2.1 Profile GPU Rendering

Profile GPU Rendering 是 Android 4.1 系统提供的开发辅助功能,可以在开发者选项中打开这一功能:设置–>开发者选项–>GPU呈现模式分析–>在屏幕上显示为条形图:

GPU呈模式分析

图中横轴代表时间,纵轴表示某一帧的耗时,绿色的横线为警戒线,超过这条线则意味着时长超过了 16ms,尽量要保证垂直的彩色柱状图保持在绿线下面。这些垂直的彩色柱状图代表着一帧,不同颜色的彩色柱状图代表不同的含义:

  • 橙色代表处理的时间,是 CPU 告诉 GPU 渲染一帧的地方,这是一个阻塞调用,因为 CPU 会一直等待 GPU 发出接到命令的回复,如果橙色柱状图很高,则表明 GPU 很繁忙;
  • 红色代表执行的时间,这部分是 Android 进行 2D 渲染 Display List 的时间,如果红色的柱状图很高,可能由于重新提交了视图而导致的。还有复杂的自定义 View 也会导致红的柱状图变高;
  • 蓝色代表测量绘制的时间,也就是需要多长时间去创建和更新 Display List。如果蓝色柱状图很高,可能需要重新绘制,或者 View.onDraw() 方法处理事情太多;

随着界面的刷新,界面上会以实时柱状图来显示每帧的渲染时间,柱状图越高表示渲染时间越长,每个柱状图偏上都有一根代表16ms基准的绿色横线,每一条竖着的柱状线都包含三部分(蓝色代表测量绘制Display List的时间,红色代表OpenGL渲染Display List所需要的时间,黄色代表CPU等待GPU处理的时间),只要我们每一帧的总时间低于基准线就不会发生UI卡顿问题(个别超出基准线其实也不算什么问题的)。

1.2.2 GPU绘制

对于UI性能的优化还可以通过开发者选项中的GPU过度绘制工具来进行分析。 在设置->开发者选项->调试GPU过度绘制(不同设备可能位置或者叫法不同)中打开调试后可以看见如下图(对settings当前界面过度绘制进行分析):

GPU绘制

以下说明:

Overdraw

蓝色(1x过度绘制),淡绿(2x过度绘制),淡红(3x过度绘制),深红(4x过度绘制)代表了4种不同程度的Overdraw情况,我们的目标就是尽量减少红色Overdraw,看到更多的蓝色区域。

Overdraw有时候是因为UI布局存在大量重叠的部分,还有的时候是因为非必须的重叠背景。例如某个Activity有一个背景,然后里面的Layout又有自己的背景,同时子View又分别有自己的背景。仅仅是通过移除非必须的背景图片,这就能够减少大量的红色Overdraw区域,增加蓝色区域的占比。这一措施能够显著提升程序性能。

如果布局中既能采用RealtiveLayoutLinearLayout,那么直接使用LinearLayout,因为Relativelayout的布局比较复杂,绘制的时候需要花费更多的CPU时间。如果需要多个LinearLayout或者Framelayout嵌套,那么可采用Relativelayout。因为多层嵌套导致布局的绘制有大部分是重复的,这会减少程序的性能。

2 布局优化工具 — Layout Inspector

使用布局检查器和布局验证工具调试布局

3 布局优化方法

布局优化方法很多,主要包括合理运用布局、includemergeViewStub

3.1 合理运用布局

常用的布局主要有 LinearLayoutRelativeLayoutFrameLayout 等,合理地使用它们可以使得 Android 绘制工作量变少,性能得到提高。举例来说:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="布局优化" />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:orientation="vertical">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Merge" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="ViewStub" />

    </LinearLayout>

</LinearLayout>

可以看到布局共三层:

布局层次

布局共 3 层,一共含有 5View,如果用 RelativeLayout 进行改写,代码如下所示:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv_text1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="布局优化" />

    <TextView
        android:id="@+id/tv_text2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_toRightOf="@+id/tv_text1"
        android:text="Merge" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_toRightOf="@+id/tv_text1"
        android:layout_below="@+id/tv_text2"
        android:text="ViewStub" />

</RelativeLayout>

可以看到布局有两层:

布局层次

布局有 2 层,一共有 4View,从这里可以看出 RelativeLayout 减少了一层的布局。如果布局复杂,可以合理的利用 RelativeLayout 来减少布局层次。RelativeLayout 的性能比 LinearLayout 低,因为 RelativeLayout 中的 View 排列方式是基于彼此依赖的。

但是,在实际开发过程中面对的情况比较多,不能轻易说谁的性能更好。在一般情况下,如果布局层数较多时,推荐使用 RelativeLayout,如果布局嵌套较多,推荐使用 LinearLayout 来实现。

3.2 使用 include 标签来进行布局复用

当多个布局需要复用一个相同的布局,比如一个 TitleBar,如果这些洁面都要加上这个相同布局 TitleBar,维护起来很麻烦,需要复制 TitleBar 的布局到每个需要添加的洁面,这样容易发生遗漏。如果修改 TitleBar 则需要去引用 TitleBar 的布局中进行修改。为了解决这些问题,可以用 include 标签来解决。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="40dp">

    <ImageView
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_gravity="center"
        android:src="@drawable/ic_launcher_background"
        android:padding="3dp" />

</LinearLayout>

这个 TitleBarImageViewTextView 组成。下面将 TitleBar 引入到此前用过的布局中,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <include layout="@layout/title_bar" />

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <TextView
            android:id="@+id/tv_text1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="布局优化" />

        <TextView
            android:id="@+id/tv_text2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:layout_toRightOf="@+id/tv_text1"
            android:text="Merge" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/tv_text2"
            android:layout_marginLeft="10dp"
            android:layout_toRightOf="@+id/tv_text1"
            android:text="ViewStub" />

    </RelativeLayout>
</LinearLayout>

可以看到布局有两层:

布局层次

3.3 用 merge 标签去除多余层级

merge 意味着合并,在合适的场景使用 merge 标签可以减少多余的层级。merge 标签一般和 include 标签搭配使用。对于上一节的例子,如果用 merge 标签来替换 LinearLayout,代码如下所示:

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="40dp">

    <ImageView
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_gravity="center"
        android:padding="3dp"
        android:src="@drawable/ic_launcher_background" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:gravity="center"
        android:text="绘制优化" />

</merge>

布局层次:

布局层次

可以看到,之前的 LinearLayout 没有了,但是这里有 merge 标签来替代 LinearLayout 会导致 LinearLayout 失效,布局会错乱。merge 标签最好是替代 FrameLayout 或者布局方向一致的 LinearLayout,比如当前父布局的 LinearLayout 的布局方向是垂直的,包含的子布局 LinearLayout 的布局防线也是垂直的,就可以用 merge 标签。但是本场景下 TitleBar 的跟布局 LinearLayout 的布局方向是水平的,显然不符合这一要求。

3.4 使用 ViewStub 来提高加载速度

一个常见的开发场景就是某个布局上并不是所有的控件都要显示出来,而是显示其中的一部分,对于这种情况,一般采用的方法就是使用 ViewGONEVISIBLE 属性,这种方法效率不高,虽然达到了隐藏的目的,但是仍在布局当中,系统仍会解析它们,可以使用 ViewStub 来解决这一问题。

ViewStub 是轻量级的 View,不可见并且不占据布局位置。当 ViewStub 调用 inflate 方法或者设置可见时,系统会夹在 ViewStub 指定的布局,然后将这个布局添加到 ViewStub 中,在对 ViewStub 调用 inflate 方法或者设置可见之前,它是不占据布局空间和系统资源的,它主要的目的就是为了目标视图占用一个位置。因此,使用 ViewStub 可以提高洁面初始化的性能,从而提高界面的加载速度。首先,在布局中加入 ViewStub 标签:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ViewStub
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:layout="@layout/title_bar" />

</LinearLayout>

ViewStub 标签中使用 android:layout 引用了此前写好的布局 title_bar.xml。在运行程序时,ViewStub 标签所引用的布局是显示不出来的,因为该布局还没有加载到 ViewStub 中,接下来在代码中使用 ViewStub

public class MainActivity extends AppCompatActivity {

    private ViewStub viewStub;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        viewStub = findViewById(R.id.view_stub);
        viewStub.inflate(); // 1
        viewStub.setVisibility(View.VISIBLE); // 2
    }
}

注释 1 和注释 2 处的嗲吗用来将 ViewStub 应用的布局家在到 ViewStub 中,这样应用布局就显示出来了。在使用 ViewStub 时需要注意以下问题:

  • ViewStub 只能加载一次,加载后 ViewStub 对象会被置为空,这样在 ViewStub 引用的布局被加载后,就不能用用 ViewStub 来控制引用的布局了。因此,如果一个控件需要不断地显示和隐藏,还是要使用 ViewVisibility 属性;
  • ViewStub 不能嵌套 merge 标签;
  • ViewStub 操作的是布局文件,如果只是想操作具体的 View,还是要使用 ViewVisibility 属性;

3.5 绘制优化

绘制优化主要是指View.onDraw方法需要避免执行大量的操作:

  • onDraw方法不需要创建新的局部对象,这是因为onDraw方法是实时执行的,产生大量的临时对象,导致占用了更多内存,并且使系统不断的GC,降低了执行效率;
  • onDraw方法不需要执行耗时操作,在onDraw方法里少使用循环,因为循环会占用CPU的时间。导致绘制不流畅,卡顿等等。 Google官方指出,View的绘制帧率稳定在60dps,这要求每帧的绘制时间不超过16ms1000/60)。虽然很难保证,但我们需要尽可能的降低;

60dps是目前最合适的图像显示速度,也是绝大部分Android设备设置的调试频率,如果在16ms内顺利完成界面刷新操作可以展示出流畅的画面,而由于任何原因导致接收到VSYNC信号的时候无法完成本次刷新操作,就会产生掉帧的现象,刷新帧率自然也就跟着下降(假定刷新帧率由正常的60fps降到30fps,用户就会明显感知到卡顿)。

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

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

相关文章

Tomcat的部署详解(基于Centos7.9))

文章目录Tomcat的部署1.1 安装jdk1.2、安装Tomcat1.3 Tomcat的目录结构1.4 Tomcat管理Tomcat web管理功能Tomcat配置文件TomcatTomcat&#xff1a;一种web服务器 Tomacat是由Apache推出的一款免费开源的Servlet容器&#xff0c;可实现JavaWeb程序的装载。 Tomcat服务器是一个…

大数据处理各组件概念及作用

一、数据采集&#xff1a; 1.1 Flume集群&#xff1a;数据采集工具&#xff0c;如写脚本将不同源端的数据采集后进行数据存储&#xff0c;或推送至Kafka等&#xff1b; 1.2 FTP集群&#xff1a;文件传输工具&#xff1b; 1.3 Kafka集群&#xff1a;消息队列&#xff0c;未避免…

UEFI启动的七阶段笔记

研究起点是CPU收到ResetVector信号后&#xff0c;开始执行第一行代码&#xff0c;一直到计算机关机/重启/崩溃&#xff0c;被划分为七个不同阶段。 1. SEC安全阶段 最早开始运行的固件代码&#xff0c;很大部分是汇编语言开发的。 主要负责四件事&#xff1a; 处理平台所有的…

纯手动搭建大数据集群架构_记录007_搭建Zookeeper3.5.7集群_集群配置_集群脚本---大数据之Hadoop3.x工作笔记0168

然后我们来搭建一下zookeeper集群,这个集群,以后不管是搭建Hadoop高可用,还是nifi集群,还是 hive 集群,hbase集群,spark集群,kettle集群,sqoop集群,都是基础 [root@hadoop122 ~]# cd /opt/software/ [root@hadoop122 software]# ll 总用量 479040 -rw-r--r--. 1 root root 9…

时间颗粒度选择(通过选择时间范围和颗粒度展示选项)

<template><div><el-time-selectplaceholder"起始时间"v-model"startTime":picker-options"startPickerOptions"change"changeStartTime"></el-time-select><el-time-selectplaceholder"结束时间&quo…

【Python】python深拷贝与浅拷贝详解(必须掌握)

深拷贝和浅拷贝是python必须要掌握的内容&#xff0c;无论你是面试开发、测试、运维等职位&#xff0c;只要是python&#xff0c;深拷贝与浅拷贝是面试官常问的一个重要知识点。 &#xff08;关注“测试开发自动化” 弓中皓&#xff0c;获取更多学习内容&#xff09; 文章目录一…

软件测试如何进行需求分析,你真的学会了吗?

目录 前言 从宏观的角度看需求文档 从细节的角度看需求文档 1&#xff09;结构化项目流程 2&#xff09;确认影响模块 3&#xff09;考量综合因素 需求的测试成本与质量风险 总结感谢每一个认真阅读我文章的人&#xff01;&#xff01;&#xff01; 重点&#xff1a;配…

Win11安卓应用自动化测试的准备

前言 APP的自动化测试&#xff0c;前提从ADB连接设备&#xff0c;启动APK应用开始。Win 11自带的WSA的APP的测试&#xff0c;要完全自动化&#xff0c;需要解决几个问题。 1、 启动WSA 此项有两步&#xff1a; i 启动WSA服务 Python程序来启动服务&#xff0c;可以直接调用…

DP1621国产LCD驱动芯片兼容替代HT1621B

目录DP1621简介DP1621芯片特性DP1621简介 DP1621是点阵式存储映射的LCD驱动器芯片&#xff0c;可支持最大128点&#xff08;32SEG * 4COM&#xff09;的 LCD屏&#xff0c;也支持2COM和3COM的LCD屏。单片机可通过3/4个通信脚配置显示参数和发送显示数据&#xff0c;也可通过指…

jmeter基本使用

jmeter基本使用 1.jemeter基本介绍 Jmeter 是什么 Apache JMeter 是 Apache 组织基于 Java 开发的压力测试工具&#xff0c;用于对软件做压力测 试JMeter 可用于 Web 应用测试&#xff0c;后来扩展到了其他测试领域具体来说, Jmeter 可以测试静态和动态资源&#xff0c;比如…

C语言Switch语句用法

C switch 语句 一个 switch 语句允许测试一个变量等于多个值时的情况。每个值称为一个 case&#xff0c;且被测试的变量会对每个 switch case 进行检查。 语法 C 语言中 switch 语句的语法&#xff1a; switch(expression){case constant-expression :statement(s);break;…

论索引的重要性

开篇小测验下面这样一个小SQL 你该怎么样添加最优索引两个表上现在只有聚集索引bigproduct 表上已经有聚集索引 ProductIDbigtransactionhistory 表上已经有聚集索引 TransactionID你是否一眼就能看出来呢&#xff1f;答案将在文章中逐步揭晓~~~简单粗暴的添加索引首先我们看一…

企业如何做好员工安全意识提升

背景&#xff1a; 近年来随着网络安全政策、技术的不断发展&#xff0c;国内企业对于安全的重视程度越来越高&#xff0c;安全建设投入力度越来越大&#xff0c;安全防御能力得到了明显的提升。然而&#xff0c;企业面临一个尴尬的问题就是&#xff0c;企业即使做了很多安全防…

Java还值得选择吗?

自1995年Java问世&#xff0c;到2023年已经差不多存在了28年。作为高级编程语言&#xff0c;他的生命周期相比很多编程语言都长&#xff0c;也见证了很多编程语言的辉煌时刻&#xff0c;不过Java始终都是名列前茅。 Java的主要优势在于其一次编写、随处运行。简单来讲&#xf…

185、【栈与队列】leetcode ——496. 下一个更大元素 I:单调栈-哈希表(C++版本)

题目描述 原题链接&#xff1a;496. 下一个更大元素 I 解题思路 本题与 739. 每日温度 的区别在于&#xff0c;需要先通过让nums1与nums2判定出为想等元素后&#xff0c;再去找nums2中更大的数。 因此&#xff0c;第一步需要找到想等数&#xff0c;第二步需要找到大于的数。…

快手电商新增商品信息诊断规则,对商家有何影响?

1、2022年快手短剧日活跃用户达2.6亿 新榜讯 近日&#xff0c;快手数据显示&#xff0c;2022年快手短剧日活跃用户达2.6亿&#xff0c;现在的付费用户数对比2022年4月增长超过480%&#xff0c;快手已经是最大的短剧消费市场。此外&#xff0c;2023年快手小游戏日活跃用户峰值超…

【Go】基于telegraf进行自定义插件开发(二)

基于telegraf进行自定义插件开发&#xff08;二&#xff09;前言正文设计开发过程单个服务的处理结构体同时定义了string和数值类型适配本机服务或者多个ip来源程序打包结语前言 书接上会&#xff0c;这次记录一下我基于telegraf进行的hdfs监控组件的开发工作&#xff0c;这其…

RabbitMQ实现延时队列

目录什么是延时队列延时队列的使用场景前提准备利用RabbitMQ实现延时队列延时队列优化利用RabbitMQ插件实现延迟队列什么是延时队列 延时队列&#xff0c;首先&#xff0c;它是一种队列&#xff0c;队列意味着内部的元素是有序的&#xff0c;元素出队和入队是有方向性的&#…

沃尔玛采购退款大额过不了的原因是什么?

市面上有很多伪装工具&#xff0c;但不是针对沃尔玛&#xff0c;很多软件方只是针对大众平台伪装&#xff0c;主要是方便程序开发人员调测系统程序&#xff0c;并不是能用于沃尔玛行业。而且每一个平台的风控是不—样的&#xff0c;我们技术团队从底层硬件环境开始配合软件控制…

【论文精读】MVSNet系列(2018-2022)总结

MVSNet系列总结1.MVSNet ECCV20182.RMVSNet CVPR20193.P-MVSNet ICCV20194.MVSCRF ICCV20195.PointMVSNet ICCV20192019年的这四篇文章各有特点&#xff0c;其中RMVSNet、PointMVSNet更是打开了可以继续沿着往下做的思路&#xff1a;6.cascade MVSNet CVPR20207.UCSNet CVPR202…