【Android】Framework笔记——屏幕刷新机制


Android 中实际上是采用了三个缓冲区用于显示的。那么为什么需要用到这么多的缓冲区呢?我们从单缓冲开始,思考每个阶段存在的问题。

单缓冲技术

我们先来谈谈单缓冲技术,单缓冲意味着屏幕上渲染的数据在一个缓冲区中绘制与显示。早期的计算机通常采用了这种设计。但这非常容易造成一种屏幕撕裂的问题:当我们的缓冲的写入速度比屏幕的渲染速度还要快的时候,就会导致显示器还没渲染完,就开始向缓冲区写入下一帧的内容,从而造成屏幕上出现撕裂的效果。

双缓冲技术

为了解决这个撕裂的问题,出现了双缓冲技术,它采用了两个缓冲区,一个用于屏幕显示(Front Buffer),一个用于 CPU/GPU 在后台进行绘制(Back Buffer),每次屏幕绘制后,就交换两缓冲区将 Back Buffer 的内容显示在屏幕上,这样就可以大大地减少这种撕裂问题,因为 CPU/GPU 的处理并不会马上影响到屏幕上显示的内容。

双缓冲技术的交换时机

但此时两个缓冲区交换的时机又成为了新的问题,何时对两个缓冲区进行交换呢?如果在 Back Buffer 准备就绪后就进行交换,很有可能此时屏幕还没有显示完成上一帧的内容。因此我们应当在屏幕对一帧的数据渲染完毕后,再执行这个交换的操作。

每次扫描完成一个屏幕后,屏幕需要回到第一行进行下一轮的扫描,这个过程中会有一个空隙时间,显然在这个时间点进行刷新就是进行缓冲区交换的最佳时间。此时屏幕并没有在进行刷新,就避免了出现屏幕闪烁的情况。因此 Android 中采用了一种名为 VSync 的技术,它能够利用这样的空隙时间来交换两个缓冲区,从而实现这样的功能。

没有 VSync 机制存在的问题

在没有引入 VSync 机制时,若 GPU / CPU 的处理出现延迟,可能出现如下图的问题:

image-20190723170424633

在上图中,每个竖直线隔开的就是一帧,而下面的两条分别代表了 CPU / GPU 正在处理的帧的数据,每次到达竖直线时,就到了屏幕刷新的时机。

那么看上图出现了什么问题:

  1. 第 0 帧正在显示时 CPU/GPU 正在处理第 1 帧的数据
  2. 第一次 VSync 到来时,CPU/GPU 已经准备好了第一帧的数据,并将其展示到屏幕上
  3. 第二次 VSync 到来时,CPU/GPU 由于一些原因没有及时处理第 2 帧数据,因此屏幕上仍然在显示第一帧的数据(这种现象称为 Jank)
  4. 第 2 帧数据准备好后,需要等到下一次 VSync 才能正常被显示在屏幕上。

可以看到,在没有 VSync 技术的情况下,若出现这种问题,屏幕会出现重复显示的问题。

使用 VSync 机制的情况

而在有 VSync 技术的情况下,一旦 VSync 信号出现,CPU 就会立马开始执行 Back Buffer 的准备,一般来说 Android 手机的屏幕都是采用 60Hz,也就是说每一帧只有 1/60s,也就是 16.6ms 的时间对缓冲区的数据进行准备。它的效果如下:

image-20190723172436618

这在 CPU/GPU 的处理快于屏幕刷新的情况下,一切都是完美的,每一帧都可以在数据准备好后显示出来。

但并不是所有硬件都有那么高的性能,使得 CPU/GPU 的处理一定快于屏幕的刷新。这时候又会出现新的问题:

image-20190723174136944

如上图所示,第一个 VSync 到来时,缓冲区 B 由于性能的原因还没有处理完毕,这就导致了第二次在屏幕中展示的仍然是缓冲区 A 的画面,第二帧显示完成后,因为 VSync 信号没有到达,要等待一段空白期才能显示出来。这是硬件瓶颈问题没办法解决,等到下一个 VSync 出现时,由于 CPU/GPU 处理时间仍然超过 16.6ms,就仍然会发现这种缓冲区交换的推迟,从而导致 Jank 出现得越来越多。

三重缓冲机制

为了解决上面的问题, Android 在 Android 4.1 中引入了三重缓冲机制,成功缓解了上面的情况造成的影响:

image-20190723175030258

如上图所示这样在准备完 B 缓冲区的数据后的空闲时间,它会在 C 缓冲区尝试处理下一帧的数据,在第二次 VSync 到来的时候,它会选择 B 的数据进行展示,而第三次 VSync 到来时,它会选择 C 的数据进行展示,而不是像双缓冲下继续采用 B 的数据,从而有效降低了 Jank 造成的影响。

上层绘制对 VSync 信号的处理

在 Android 4.1 之前,上层的绘制是无法接收到 VSync 信号的到来的,VSync 信号只控制了 Buffer 的切换,但上层的绘制节奏与下层屏幕的切换节奏是完全脱离开的,因此 Google 为了实现上层对 VSync 信号的接收及处理,设计了一个 Choreographer 类作为上层绘制的节奏指导者,它会向 SurfaceFlinger 注册一个 VSync 信号的接收器DispatchEventListener,每当 Framework 层收到一个 VSync 信号,都会通知到 Choreography,而 Chorerography 内部维护了一个 CallbackQueue,每当其收到一个 VSync 信号时,都会调用其 Callback 的 run 方法,从而实现了上层对 VSync 信号的监听。

之前的文章 【Android】Framework笔记——View的绘制流程(一)中所提到的 View 的绘制中 Traversal 的执行就是通过向 Choreography 注册 TraversalRunnable 而实现的,从而实现了 View 的绘制也是在每次 VSync 信号到来时执行的。


Android Developer in GDUT