Android 系统的显示原理
Android 显示过程可以简单概括为:应用层负责绘制,系统层负责渲染,通过进程间通信把应用层需要绘制的数据传递到系统层服务,系统层服务通过刷新机制把数据更新到屏幕上。
详细过程如下:
- 首先每一个应用程序都会与系统层的SurfaceFlinger建立连接
- 建立连接后会创建一块匿名共享内存SharedClient
- 在SharedClient中创建若干个SharedBufferStack。一个SharedBufferStack中有2个缓冲区(android4.1之后3个),一块SharedClient最多可以创建31个SharedBufferStack
- 一个SharedBufferStack就连接着一个应用层的surface,也就是一个window,这里也说明了为什么一个应用程序最多只能有31个window
- 应用层通过三个核心步骤:Measure、Layout、Draw将数据处理后放在surface中缓存
- 系统层在通过SharedClient得到客户端的数据后,系统级进程中的 SurfaceFlinger 服务通过刷新机制把数据渲染到屏幕上。
刷新机制
一般我们在绘制UI的时候,都会采用一种称为“双缓冲”的技术。双缓冲意味着要使用两个缓冲区(SharedBufferStack中),其中一个称为Front Buffer,另外一个称为Back Buffer。UI总是先在Back Buffer中绘制,然后再和Front Buffer交换,渲染到显示设备中。理想情况下,这样一个刷新会在16ms内完成(60FPS),下图就是描述的这样一个刷新过程(Display处理Front Buffer,CPU、GPU处理Back Buffer)
但现实情况并非这么理想。
1. 时间从0开始,进入第一个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)。
双缓冲的关键问题在于CPU可能是在忙别的事情,不知道该到处理UI绘制的时间了。可CPU一旦想起来要去处理第2帧数据,时间又错过了!
为解决这个问题,Android 4.1中引入了VSYNC。结果如下图所示:
由上图可知,每收到VSYNC中断,CPU就开始处理各帧数据。整个过程非常完美。
不过,仔细琢磨后却会发现一个新问题:上图中,CPU和GPU处理数据的速度似乎都能在16ms内完成,而且还有时间空余,也就是说,CPU/GPU的FPS(帧率,Frames Per Second)要高于Display的FPS。确实如此。由于CPU/GPU只在收到VSYNC时才开始数据处理,故它们的FPS被拉低到与Display的FPS相同。但这种处理并没有什么问题,因为Android设备的Display FPS一般是60,其对应的显示效果非常平滑。
如果CPU/GPU的FPS小于Display的FPS,会是什么情况呢?请看下图:
由上图可知:
1. 在第二个16ms时间段,Display本应显示B帧,但却因为GPU还在处理B帧,导致A帧被重复显示。
2. 同理,在第二个16ms时间段内,CPU无所事事,因为A Buffer被Display在使用。B Buffer被GPU在使用。注意,一旦过了VSYNC时间点,CPU就不能被触发以处理绘制工作了。
为什么CPU不能在第二个16ms处开始绘制工作呢?原因就是只有两个Buffer(Android 4.1之前)。如果有第三个Buffer的存在,CPU就能直接使用它,而不至于空闲。出于这一思路就引出了三重缓冲区(Android 4.1)。结果如下图所示:
由上图可知:
第二个16ms时间段,CPU使用C Buffer绘图。虽然还是会多显示A帧一次,但后续显示就比较顺畅了。在第二个时间段内,CPU绘制的第C帧数据要到第四个16ms才能显示,这比双Buffer情况多了16ms延迟。所以,Buffer最好还是两个,三个足矣。