2.1.2 刷新机制
Google发布Android操作系统后,Android OS系统一直在不断优化、更新。但直到Android 4.0版本发布,有关UI显示不流畅的问题仍未得到根本解决。在整个Android版本升级过程中,Android在显示系统方面做了不少优化和改进,比如支持硬件加速等技术,但本质原因似乎和硬件关系并不大,也没有得到太多改善。而与高端硬件配置的Android机器价格相近的iPhone,其UI的流畅性强却是有目共睹的。
从Android 4.1(Jelly Bean)开始,Android OS开发团队便力图在每个版本中解决一个重要问题。作为严重影响Android口碑问题之一的UI流畅性差的问题,首先在Android 4.1版本中得到了有效处理。其解决方法即在4.1版本推出的Project Butter。Project Butter对Android Display系统进行了重构,引入三个核心元素:VSYNC、Triple Buffer和Choreographer。其中,VSYNC是理解Project Buffer的核心。VSYNC是Vertical Synchronization(垂直同步)的缩写,是一种在PC上已经很早就广泛使用的技术,读者可简单地把它认为是一种定时中断。Choreographer起调度的作用,将绘制工作统一到VSYNC的某个时间点上,使应用的绘制工作有序。接下来,本文将围绕VSYNC来介绍Android Display系统的工作方式。
在讲解刷新机制之前,先介绍几个名词以及VSYNC和Choreographer主要功能及工作方式。
□ 双缓冲:显示内容的数据内存,为什么要用双缓冲,我们知道在Linux上通常使用Framebuffer来做显示输出,当用户进程更新Framebuffer中的数据后,显示驱动会把Framebuffer中每个像素点的值更新到屏幕,但这样会带来一个问题,如果上一帧的数据还没有显示完,Framebuffer中的数据又更新了,就会带来残影的问题,给用户直观的感觉就会有闪烁感,所以普遍采用了双缓冲技术。双缓冲意味着要使用两个缓冲区(在SharedBufferStack中),其中一个称为Front Buffer,另外一个称为Back Buffer。UI总是先在Back Buffer中绘制,然后再和Front Buffer交换,渲染到显示设备中。即只有当另一个buffer的数据准备好后,通过io_ctrl来通知显示设备切换Buffer。
□ VSYNC:从前面的双缓冲介绍中可以了解到,只有当另一个buffer准备好后,才能通知刷新,这就需要CPU以主动查询的方式来保证数据是否准备好,因为这种机制效率很低,所以引入了VSYNC。VSYNC是Vertical Synchronization(垂直同步)的缩写,可以简单地把它认为是一种定时中断,一旦收到VSYNC中断,CPU就开始处理各帧数据。
□ Choreographer:收到VSYNC信号时,调用用户设置的回调函数。一共有以下三种类型的回调:
● CALLBACK_INPUT:优先级最高,与输入事件有关。
● CALLBACK_ANIMATION:第二优先级,与动画有关。
● CALLBACK_TRAVERSAL:最低优先级,与UI控件绘制有关。
接下来通过时序图来分析刷新的过程,这些时序图是Google在2012 Google I/O讲解新的显示系统提供的,图2-7所示的时序图有三个元素:Display(显示设备), CPU-CPU准备数据,GPU-GPU准备数据。最下面的时间为显示时间,根据理想的60FPS,以16ms为一个显示周期。
图2-7 没有VSync信息的刷新
(1)没有VSync信号同步
我们以16ms为单位来进行分析:
1)从第一个16ms开始看,Display显示第0帧,CPU处理完第一帧后,GPU紧接其后处理继续第一帧。三者都在正常工作。
2)时间进入第二个16ms:因为在上一个16ms时间内,第1帧已经由CPU、GPU处理完毕。所以Display可以正常显示第1帧。显示没有问题,但在本16ms期间,CPU和GPU并未及时绘制第2帧数据(前面的空白区在忙别事情去了),而是在本周期快结束时,CPU/GPU才去处理第2帧数据。
3)时间进入第3个16ms,此时Display应该显示第2帧数据,但由于CPU和GPU还没有处理完第2帧数据,故Display只能继续显示第一帧的数据,结果使得第1帧多画了一次(对应时间段上标注了一个Jank),这就导致错过了显示第二帧。
通过上述分析可知,在第二个16ms时,发生Jank的关键问题在于,为何在第1个16ms段内,CPU/GPU没有及时处理第2帧数据?从第二个16ms开始有一段空白的时间,可以说明原因所在,那就是CPU可能是在忙别的事情,不知道该到处理UI绘制的时间了。可CPU一旦想起来要去处理第2帧数据,时间又错过了。为解决这个问题,4.1版本推出了Project Butter,核心目的就是解决刷新不同步的问题。
(2)有VSync信号同步
加入VSync后,从图2-8可以看到,一旦收到VSync中断,CPU就开始处理各帧的数据。大部分的Android显示设备刷新率是60Hz(图2-7的时间轴也是60ms),这也就意味着每一帧最多只能有1/60=16ms左右的准备时间。假如CPU/GPU的FPS高于这个值,显示效果将更好。但是,这时又出现了一个新问题:CPU和GPU处理数据的速度都能在16ms内完成,而且还有时间空余,但必须等到VSYNC信号到来后,才处理下一帧数据,因此CPU/GPU的FPS被拉低到与Display的FPS相同。
图2-8 有VSync的绘制
从图2-9采用双缓冲区的显示效果来看:在双缓冲下,CPU/GPU FPS大于刷新频率同时采用了双缓冲技术以及VSync,可以看到整个过程还是相当不错的,虽然CPU/GPU处理所用的时间时短时长,但总体来说都在16ms以内,因而不影响显示效果。A和B分别代表两个缓冲区,它们不断交换来正确显示画面。但如果CPU/GPU的FPS小于Display的FPS,情况又不同了,如图2-10所示。
图2-9 双缓冲下的时序图
图2-10 双缓冲下CPU/GPU FPS小于刷新频率时序图
从图2-10可以看到,当CPU/GPU的处理时间超过16ms时,第一个VSync就已经到来,但缓冲区B中的数据却还没有准备好,这样就只能继续显示之前A缓冲区中的内容。而后面B完成后,又因为还没有VSync信号,CPU/GPU这个时候只能等待下一个VSync的来临才开始处理下一帧数据。因此在整个过程中,有一大段时间被浪费。总结这段话就是:
1)在第二个16ms时间段内,Display本应显示B帧,但因为GPU还在处理B帧,导致A帧被重复显示。
2)同理,在第二个16ms时间段内,CPU无所事事,因为A Buffer由Display在使用。B Buffer由GPU使用。注意,一旦过了VSYNC时间点,CPU就不能被触发以及处理绘制工作了。
为什么CPU不能在第二个16ms处即VSync到来就开始工作呢?很明显,原因就是只有两个Buffer。如果有第三个Buffer存在,CPU就可以开始工作,而不至于空闲。于是在Andoird 4.1以后,引出了第三个缓冲区:Triple Buffer。Triple Buffer利用CPU/GPU的空闲等待时间提前准备好数据,并不一定会使用。
注意
在大部分情况下,只使用到双缓存,只有在需要时,才会用三缓冲来增强,这时可以把输入的延迟降到最少,保持画面的流畅。
引入Triple Buffer后的刷新时序如图2-11所示。
图2-11 使用Triple Buffer时序图
在第二个16ms时间段,CPU使用C Buffer绘图。虽然还是会多显示一次A帧,但后续显示就比较顺畅了。是不是Buffer越多越好呢?回答是否定的。由图2-11可知,在第二个时间段内,CPU绘制的第C帧数据要到第四个16ms才能显示,这比双缓存情况多了16ms延迟。所以缓冲区不是越多越好,要做到平衡到最佳效果。
从以上的分析来看,Android系统在显示机制上解决了Android UI不流畅的问题,并且从Google I/O2012给出的视频来看,其效果也达到预期。但实际在应用开发过程中仍然存在卡顿的现象。因为VSync中断处理的线程优先级一定要最高,否则即使接收到VSync中断,不能及时处理,也是徒劳无功。