性能优化<第四篇>:UI绘制优化
1、视图显示的过程
CPU:主要用于运算,比如:视图的位置大小的计算;
GPU:主要负责像素填充;
视图显示的过程:一个已知大小的视图,它的大小和位置被CPU计算之后,处理成多维的向量图形(矢量图),CPU将显示图像的工作交给GPU来处理,GPU将多维的向量图形进行栅格化,转换成若干各像素(位图)作用于显示器。
将向量图形(矢量图)转换成若干个像素(位图)的过程又叫做`栅格化`。
2、刷新率
首先来看下UI组件的绘制过程:

从上图上可以看出,UI在手机上显示是16ms刷新一次,这到底是怎么计算的呢?
实际上,在与手机交互过程中,经过长期的感知,认为60帧以下人眼是能够感觉出来的。
目前市面上大部分手机的刷新率是60HZ,也就是60fps(60帧每秒)。
1000ms / 60 = 16.66ms,经过计算,每16.66ms刷新一次。
上图的16ms就是这样计算的来的。
市面上有些手机的刷新率可以设置为120HZ, 也就是120fps(120帧每秒)。
1000ms / 120 = 8.33ms, 经过计算,每8.33ms刷新一次。
3、卡顿原因分析
首先来看下图,理解下为什么会出现卡顿

如上图所示,手机每16ms刷新一次;
第一次刷新:CPU的计算时间 和 GPU的栅格化(渲染)在第一次刷新之前就已经完成,所以等到16ms时间一到就会立即刷新,并不会造成卡顿;
第二次刷新:由于GPU还没有渲染完成,导致显示器无法将第二帧的画面显示在屏幕上,垂直同步机制让显示器等待GPU绘制完成,所以当前显示器依然显示第一帧的画面,这就是典型的卡顿现象。
4、什么情况容易造成卡顿?
(1)布局嵌套过深,导致CPU计算时间过长;
(2)存在过度绘制现象,即在某一块区域,被绘制了多次,然后用户只能看到最上层的渲染图层,底层的根本就看不到,从而造成了资源的浪费;
5、布局优化
一个思想:强烈抵制超过3层的布局嵌套,布局应尽量做到嵌套层次不要超过3层;
如果布局太过复杂,可以通过工具查看布局,推荐的工具有:
(1)在Android SDK 中找到:Android/sdk/tools/bin/uiautomatorviewer.bat
效果展示:

(2)在Android SDK 中找到:Android\sdk\tools\monitor.bat
点击左边的手机图标可以查看布局:

(3)Layout Inspector(在Android Studio的Tools菜单可以找到)

(4)打开工具monitor.bat,找到Hierarchy view按钮

其中,布局中存在三个点,如图:

三个点分别代表着View的Measure 、Layout、Draw。
绿: 表示该View的此项性能比该View Tree中超过50%的View都要快;
黄: 表示该View的此项性能比该View Tree中超过50%的View都要慢;
红: 表示该View的此项性能是View Tree中最慢的;
以上就是4种常用的查看布局的工具了,如果要做布局优化的话,第四种工具还是非常好用的,因为它可以直接看出哪个布局渲染耗时。
几点建议:
(1)能在一个页面上显示的内容,尽量只用一个容器,可以选择约束布局;
(2)尽可能把相同的容器合并(merge);
(3)能复用的代码,用include处理,可以减少GPU重复工作;
6、布局三剑客
布局三剑客分别是:include、merge、ViewStub
include:include可以使布局只会计算一次,它会保存到缓存,下次再次使用该布局时,直接拿来显示即可,不需要做额外的计算;(此为一剑)
merge:常用于修饰include的根布局,merge并不是view,没有大小,它的意义是,可以减少一层布局;(此为二剑)
ViewStub: 是一个轻量级的View,没有尺寸,不绘制任何东西,因此绘制或者移除时更省时。ViewStub实现View的延迟加载,避免资源的浪费,减少渲染时间,在需要的时候才加载View。(ViewStub不可见,大小为0,此为三剑)
ViewStub会预加载一个布局,该布局不能存在merge。
7、过度绘制优化
在我们手机中可以查看过度绘制的效果,可以在开发者选项中寻找:
开发者选项 -> Profile GPU rendering/调试GPU过度绘制
过度绘制主要影响的是GPU,GPU性能的提升主要有三个知识点:
(1)设置背景需谨慎
1、布局存在多层嵌套的情况下,不要为每个布局都设置背景,一些不需要设置背景的布局,直接将背景置为null
在某布局中将背景置为null:android:background="@null"
在setContentView之前将window的背景置为null:getWindow().setBackgroundDrawable(null)
在所有Activity的主题中去掉window背景:<item name="android:windowBackground">@null</item>
一般情况下,window背景都要设置为null,除非业务需要。
(2)使用裁减减少控件之间的重合部分
在自定义View时,在onDraw方法中尽量避免在同一块区域绘制多层,这样会导致看不到的地方也需求渲染,浪费GPU的资源。可以通过裁剪的方式,尽量做到一块区域只有一层图层;
(3)Android7.0之后系统做出的优化:invalidate()不再执行测量和布局动作。
[完...]