面试

Android性能优化-界面渲染原理浅析

2018-09-29  本文已影响13人  niknowzcd

app应用作为一个离用户最近的应用,其流畅度是至关重要的。谷歌官方在每个版本的更新中都有关于流畅度的优化,其中android4.1是一个里程,在这个版本中,提出了Project Butter概念。

Project Butter对Android Display系统进行了重构,引入了三个核心元素,即VSYNC、Triple Buffer和Choreographer。

为何是16ms

android系统每隔16ms更新一次UI,相当于每秒更新60次,这也是我们常说的60帧的由来。程序设置为60帧刷新是因为普通人的人眼与大脑之间的协作无法感知超过60fps的画面更新。

如何渲染界面的

想要了解更多可以参考 CPU 和 GPU 的区别是什么?

那么Android是如何把图像绘制到界面上的呢?

这里需要引入一个Resterization栅格化的概念。

image

Resterization栅格化是绘制那些Button,Shape,Path,String,Bitmap等组件最基础的操作。它把那些组件拆分到不同的像素上进行显示。这是一个很费时的操作,GPU的引入就是为了加快栅格化的操作。

CPU负责把UI组件计算成Polygons,Texture纹理,然后交给GPU进行栅格化渲染。

整个流程如下

image

然而每次从CPU转移到GPU是一件很麻烦的事情,所幸的是OpenGL ES可以把那些需要渲染的纹理Hold在GPU Memory里面,在下次需要渲染的时候直接进行操作。所以如果你更新了GPU所hold住的纹理内容,那么之前保存的状态就丢失了。

在Android里面那些由主题所提供的资源,例如Bitmaps,Drawables都是一起打包到统一的Texture纹理当中,然后再传递到GPU里面,这意味着每次你需要使用这些资源的时候,都是直接从纹理里面进行获取渲染的。当然随着UI组件的越来越丰富,有了更多演变的形态。例如显示图片的时候,需要先经过CPU的计算加载到内存中,然后传递给GPU进行渲染。文字的显示更加复杂,需要先经过CPU换算成纹理,然后再交给GPU进行渲染,回到CPU绘制单个字符的时候,再重新引用经过GPU渲染的内容。动画则是一个更加复杂的操作流程。

为了能够使得App流畅,我们需要在每一帧16ms以内处理完所有的CPU与GPU计算,绘制,渲染等等操作。

负责界面渲染的容器DisplayList

通常来说,Android需要把XML布局文件转换成GPU能够识别并绘制的对象。这个操作是在DisplayList的帮助下完成的。DisplayList持有所有将要交给GPU绘制到屏幕上的数据信息。

在某个View第一次需要被渲染时,DisplayList会因此而被创建,当这个View要显示到屏幕上时,我们会执行GPU的绘制指令来进行渲染。如果你在后续有执行类似移动这个View的位置等操作而需要再次渲染这个View时,我们就仅仅需要额外操作一次渲染指令就够了。然而如果你修改了View中的某些可见组件,那么之前的DisplayList就无法继续使用了,我们需要回头重新创建一个DisplayList并且重新执行渲染指令并更新到屏幕上。

需要注意的是:任何时候View中的绘制内容发生变化时,都会重新执行创建DisplayList,渲染DisplayList,更新到屏幕上等一系列操作。这个流程的表现性能取决于你的View的复杂程度,View的状态变化以及渲染管道的执行性能。举个例子,假设某个Button的大小需要增大到目前的两倍,在增大Button大小之前,需要通过父View重新计算并摆放其他子View的位置。修改View的大小会触发整个HierarcyView的重新计算大小的操作。如果是修改View的位置则会触发HierarchView重新计算其他View的位置。如果布局很复杂,这就会很容易导致严重的性能问题。我们需要尽量减少Overdraw。

image

Android Display工作方式

没有VSYNC的情况, CPU和GPU比较“任性”,当他们处于空闲状态时才会处理数据

image

出现上述问题的原因,究其原因还是CPU和GPU没能及时处理数据,为了解决这个问题,引入了VSYNC

加入VSYNC(垂直同步)之后,Display的工作方式

文章开头说过,VSYNC的作用是定时产生一个中断信号,用来提醒CPU和GPU要开始工作了。

image

如图所图,在每个时间间隔的开始,CPU和GPU开始工作,处理下一帧的数据,用于Display显示.正常情况下界面都能正常且平滑的显示.

但如果在一个时间间隔即(16ms)内CPU和GPU处理不完下一帧的数据,还是会出现卡顿的情况,如下图

image

T0,T1显示的都是第一帧的数据,T2,T3显示的都是第二帧的数据,刷新率从16ms变大到了32ms,从而引起卡顿感觉。另外在T1阶段时,CPU并没有进入工作,这是因为早期系统设计的是双Buffer,此刻A Buffer被Display使用,B Buffer被GPU在用,所以CPU无事可做,只能闲置,另外由于VSYNC的存在,过了VSYNC的时间点,就算有多余的Buffer存在,CPU也不会重新进入工作.

于是就有了Triple Buffer,Triple Buffer的作用是当双Buffer不够时,分配第三个Buffer。

image

在加入C Buffer之后,在T1这个时间点CPU就不至于处于闲置状态,虽然在T1时间,Display绘制的还是第一帧的数据,不过接下来的几帧就显得比较顺畅了。

既然多Buffer的作用明显,那为什么不多加几个Buffer呢?实际上Buffer并不是越多越好的,由上图可知,三个Buffer已经能解决大部分问题了,追加更多的Buffer并不能有效的提高效率,反而会因为多加的Buffer影响效率。

Choreographer: 用来接受一个VSYNC信号来统一协调UI更新

ChoreographerProject Butter也十分重要,除了统一协调UI更新之外,还可以用来监测UI是否发生卡顿

Android系统每隔16.6ms发出VSYNC信号,来通知界面进行输入、动画、绘制等动作,每一次同步的周期为16.6ms,代表一帧的刷新频率,理论上来说两次回调的时间周期应该在16.6ms,如果超过了16.6ms我们则认为发生了卡顿,利用两次回调间的时间周期来判断是否发生卡顿 这个方案的原理主要是通过Choreographer类设置它的FrameCallback函数,当每一帧被渲染时会触发回调FrameCallback, FrameCallback回调void doFrame (long frameTimeNanos)函数。一次界面渲染会回调doFrame方法,如果两次doFrame之间的间隔大于16.6ms说明发生了卡顿。

具体可以参考. Android 流畅度检测原理简析

参考文章

Android N中UI硬件渲染(hwui)的HWUI_NEW_OPS(基于Android 7.1)

Android Project Butter分析

Android性能优化典范

Android 流畅度检测原理简析

上一篇下一篇

猜你喜欢

热点阅读