你好, 我是老七, 很高兴能够分享一些我在学习过程中的收获, 本文是学习 Google 官方渲染 UI 的学习笔记, 如果本文帮助到你了, 希望不要吝啬你的小小喜欢点个心, 你的支持是我坚持的动力, 接下来就步入正题了, 集中你的注意力, 性能优化之车要发车了~
性能渲染的定义
Android 系统每隔 16ms 重新绘制一次 Activity, 也就意味着应用需要在 16ms 内完成屏幕刷新的全部逻辑操作, 这样才能达到每秒 60 帧.
1000ms/60hz = 16.666ms/frame
image.PNG
这个每秒帧数的参数实际上来源于手机硬件, 定义了屏幕每秒刷新速度有多快, 这意味着你有 60ms 的时间去完成每帧的绘制逻辑操作, 如果错过了, 比如你花费了 24ms 才完成计算, 那么就会出现我们所称之为丢帧的情况.
image.PNG
Android 系统尝试在屏幕上绘制新的一帧, 但是这一阵还没有准备好, 所以画面就不会刷新, 就会造成用户盯着同一张图看了 32ms 而不是 16ms, 丢帧情况下运行的任何动画就会使用户很容易察觉出卡顿感, 哪怕仅仅出现一次丢帧, 用户都会发现动画不是很流畅, 如果出现多次丢帧, 用户就会开始抱怨卡顿, 如果此时用户正在和系统进行交互操作, 例如滑动列表或者输入数据, 那么卡顿感就会更加明显, 用户就会开启吐槽模式~
只有我们对绘制每帧花费的时间有更清晰的了解后才能发现是什么原因导致了卡顿, 该如何去解决应用中的这些问题.
渲染管道
Android 系统的渲染管道分为两个关键组件: CPU,GPU
image.PNG
两者共同工作在屏幕上绘制图片, 每个组件都有自身定义的特定流程, 你必须遵守这些特定的操作规则才能达到效果.
常见的性能问题
CPU
最常见的性能问题是不必要的布局和失效, 这些内容必须在师徒层次结构中进行测量, 清除并重新创建, 而引发这种问题通常有两个原因, 一是重建显示列表的次数太多, 二是花费太多时间作废视图层次并进行不必要的重绘. 这两个原因在更新显示列表, 或者其他缓存 GPU 资源时导致 CPU 工作过度.
GPU
最常见的问题就是过度绘制, 通常是在像素着色过程中, 通过其他工具进行后期着色时, 浪费了 GPU 的处理时间 [图片上传中...(image.PNG-d82fb7-1538205529526-0)]
接下来将介绍更多关于失效, 布局和重绘的内容以及如何使用 SDK 中提供的可用工具找出影响应用性能的原因并且举例说明如何修复应用中的此类问题.
过度绘制
要想开发一款性能优越的应用, 你必须了解底层是如何运行的, 如果不知道硬件是如何运行就无法熟练使用它.
首先我们要知道: Activity 是如何绘制到屏幕上的, 那些负责的 xml 布局文件和标记语言是如何转化成用户能看懂的图像的?
实际上, 这是由光栅化操作来完成的.
image.PNG
光栅化将注入字符串, 按钮, 路径或者形状的一些高级对象拆分到不同的像素上在屏幕上进行显示, 并且光栅化是一个非常费时的操作. 也就是说你的手机里有一块特殊硬件, 目的就是加快光栅化的操作, 图像处理单元, 也就是 GPU.
GPU 是在上个世纪 90 年代被引入主流电脑, 帮助加快光栅化操作, 现在, GPU 使用一些指定的基础指令集, 主要是多边形和纹理, 也就是图片, CPU 在屏幕上绘制图像前会向 GPU 输入这些指令, 这一过程通常使用的 API 就是 Android 的 OpenGL ES, 这就是说, 在屏幕上绘制 UI 对象时, 无论是按钮, 路径, 或者复选框都需要在 CPU 中首先转换为多边形或者纹理, 然后再传递给 GPU 进行光栅化.
你可以想象一下, 一个 UI 对象转换为一系列多边形和纹理的过程, 肯定是相当耗时的, 从 CPU 上传处理数据到 GPU 同样也很耗时, 所以很明显, 你需要尽量减少对象转换的次数以及上传数据的次数, 幸亏 OpenGL ES 的 API 允许数据上传到 GPU 后可以对数据进行保存, 当你下次绘制一个按钮时, 只需要在 GPU 存储器里引用它, 然后告诉 OpenGL 如何绘制.
到了这里我们就可以应该可以想到, 渲染性能的优化就是尽可能快的上传数据到 GPU, 然后尽可能长地在不修改的条件下保存数据, 因为每次上传资源到 GPU 时, 你都会浪费宝贵的处理时间.
Android 系统的 Honeycomb(API Level 11) 版本发布之后, 整个 UI 渲染系统就在 GPU 中运行, 之后各个版本都在渲染系统性能方面有更多改进, Android 系统在降低, 重新利用 GPU 资源方面做了很多工作, 所以在这方面我们完全不用担心, 举个例子说, 任何你的主题所提供的资源, 例如 Bitmaps,Drawables 等都是一起打包到统一的纹理当中, 然后利用网格工具上传到 GPU, 例如 Nine Patches 等, 这样每次你需要绘制这些资源时就不用做任何转换, 因为他们已经存储在 GPU 中了, 大大加快了这些视图类型的显示.
然而随着 UI 对象的不断升级, 渲染流程也变得越来越复杂, 例如说绘制图像就是把图片上传到 CPU 存储器, 然后传递到 GPU 中进行渲染, 路径使用是完全另一回事, 你需要在 CPU 中创建一系列的多边形, 甚至在 GPU 中创建掩蔽纹理来定义路径, 绘制字符更加复杂一些, 首先我们需要在 CPU 中把字符绘制成图像, 然后把图像上传到 GPU 进行渲染再返回到 CPU, 在屏幕上为字符串的每个字符绘制一个正方形.
现在 Android 系统已经解决了大多数性能问题, 除非你还有更高的要求, 你基本不会发现与 GPU 相关的问题, 然而还有一个 GPU 性能问题瓶颈, 这个问题困扰着每个程序开发人员, 这就是过度绘制.
如果你曾经画过一个房间或房子, 你应该知道在那些墙上涂满颜色会花费很多功夫, 如果你需要重新画一遍, 那你第一次做这件事时就浪费了很多功夫. 相同的, 浪费精力去绘制某些东西同样可能会对应用程序中的性能问题产生影响, 所以, 在性能和设计的交汇处存在一个共同的性能问题 - 过度绘制.
image.PNG
过度绘制是一个术语, 用来描述屏幕上的一个像素在一帧中被重画了多少次, 例如, 如果我们有一堆层叠的 UI, 上面的 UI 层级会遮盖住底下的 UI 层级, 意味着我们花费很多时间绘制的图层大部分是不可见的, 实际上这是一个很大的问题, 因为每次我们渲染的像素对最终场景没有帮助, 我们就浪费了 GPU 的性能. 使用这样的布局, 我们很容易陷入一个陷阱. 分层的视图给了我们这个美丽的, 卓越的设计, 但同样也导致了过渡绘制的问题. 为了最大化应用程序的性能, 你需要使用最小化的过渡绘制.
image.PNG
幸运的是, 在 Android 设备上很容易看到应用程序中过度绘制的数量. 进入手机的开发者模式, 打开 GPU 过渡绘制的功能, 你的手机界面可能会产生视觉上的一些变化, 因为 Android 使用不同的颜色高亮显示过渡绘制的区域, 如果你只在某个像素上绘制了一次, 那么将不会有任何颜色, 然而, 随着过度绘制的增加, 颜色也会改变. 依据过度绘制的层度可以分成:
无过度绘制 (一个像素只被绘制了一次)
过度绘制 x1(一个像素被绘制了两次)
过度绘制 x2(一个像素被绘制了三次)
过度绘制 x3(一个像素被绘制了四次)
过度绘制 x4+(一个像素被绘制了五次以上
image.PNG
优化方案
首先, 你需要从视图中删除对最终呈现的图像没有帮助背景和绘图, 因为这属于浪费性能.
接下来, 你可以定义你知道会隐藏部分视图的屏幕区域, 这有助于降低 CPU 和 GPU 开销
方案演示
上面说了那么多理论性的内容, 光说不练假把式, 接下来我们真刀真枪的干上那么一干. 附上了练习项目的地址, 大家可以下载下来也动手试试.
练习项目
现在我们打开应用可以看见这些红色的过渡绘制的区域, 我们的任务就是减少这些过度绘制.
image.PNG
按照文中之前说的, 我们需要先去了解一下 UI 是如何创建的并且试着做一些清理减少过渡绘制, 试着清除不必要的背景和图片.
分析发现, 我们的整体背景现在是蓝色过度绘制级别的, 而导致过度绘制的原因是在 ChatumLatinumActivity 中使用了不透明白色背景的布局填充了整个屏幕, 而 Android 的主题会默认设置颜色. 因此就导致了不必要的过度绘制.
image.PNG
因为我们可能需要自己设置我们应用的背景颜色, 所以就需要将主题中的背景颜色取消, 我们使用
getWindow().setBackgroundDrawable(null);
来取消原来的背景, 这个方法的作用就是去除 Windows 也就是 DecorView 的背景颜色. 效果如下. 变成了蓝色过度绘制级别.
image.PNG
接下来就可以仔细的去看看其他的 xml 文件是否仍有可以清除的不必要的背景颜色了.
暂时先整理到这. 后续会尽快更新.
来源: https://juejin.im/entry/5baf5c046fb9a05d035be9f1