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