刷新率:每秒屏幕刷新次数。
帧率:GPU 在一秒内绘制的帧数。
虽然现在有的厂商推出了高刷新率的手机,但是主流的还是 60Hz,即1秒显示60帧,1000ms / 60 frames ≈ 16.67 ms/frames,为了保证 App 的流畅度,我们应该尽量让每帧的绘制时间不超过 16ms。
Android 的显示过程可以简单概括为:应用程序把经过 measure(测量)、layout(布局)、draw(绘制)后的 surface 缓存数据,通过 SurfaceFlinger 把数据渲染到显示屏幕上,通过 Android 的刷新机制来刷新数据。换言之,应用层负责绘制,系统层负责渲染,通过进程间通信把应用层需要绘制的数据传递到系统层服务,系统层通过刷新机制把数据更新到屏幕上。
以下是有关概念的解释:
在 Android 中每个 view 都会经过 measure 和 layout 来确定其所在的大小和位置,然后绘制到 surface (缓冲区上),绘制是由 ViewRootImpl 类中 performTraversals() 方法发起的。
Android支持两种绘制方式: 和 。硬件极速从 Android 3.0 开始支持,它在 UI 显示和绘制效率方面远高于软件绘制,但是它的也有缺点:
经过多次绘制后,要显示的 view 相关的数据存储(如大小和位置)在 Surface 的缓冲区中,接下来渲染操作交由系统进程中的 SurfaceFlinger 服务来完成,这是一个 IPC(进程间通信)过程。SurfaceFlinger 的主要工作流程如下:
当 Android 应用层在图形缓冲区中绘制好 View 层次结后,应用层通过 Binder 机制与 SurfaceFlinger 通信并借助一块匿名共享内存把图形缓冲区交给 SurfaceFlinger 服务。由于单纯的匿名共享服务在传递多个窗口数据时缺乏有效的管理,所以匿名共享内存就被抽象为一个更上层的数据结构——SharedClient,在 SharedClient 中,最多有 31 个 SharedBufferStack,每个 SharedBufferStack 都对应一个 Surface 即一个 Window。 这表明一个 Android 应用程序最多可以包含 31 个 window 。
绘制的过程首先是 CPU 准备数据(measure、layout等),GPU 负责栅格化、渲染。因为图像 API 不允许 CPU 直接与 GPU 通信,所以要通过一个图形驱动的中间层来进行连接。图形驱动里面维护了一个队列,CPU 把 display list(待显示的数据列表)添加到队列中,GPU 从这个队列中取出数据进行绘制,最终在屏幕上显示出来,如下图所示:
Android 系统每隔 16ms 会发出 VSYNC 信号,触发对 UI 进行渲染,如果每次都渲染成功,就能够达到流畅画面所需的 60PS。
双缓冲顾名思义是有两个缓冲区(上文提到的 SharedBufferStack),分别是 FontBuffer(又叫作 FrameBuffer) 和 BackBuffer。UI 总是先在 Back Buffer 中绘制,然后再和 Font Buffer 交换,渲染到显示设备中,即只有当另一个 buffer 的数据准备好后,才会通过系统调用来通知显示设备切换 Buffer。
双缓冲机制在大部分情况下是适用的,但是如果某个环节出现了问题,CPU 资源就有可能存在浪费,如下图所示:
VSYNC 类似与时钟中断。竖线分割的部分代表 16ms 的时间段。正常情况下,在每一时间段内,Display 显示一帧数据(即每秒60帧)。
上图中在第二个 16ms 时间段内,Display 本应显示 B 帧,但是因为 GPU 还在处理 B 帧,导致 A 帧被重复显示。与此同时,在第二个时间段内,处于 CPU 处于空闲状态,造成了浪费。因为 A Buffer 被 Diaplay 在使用(SufaceFlinger 用完后不会释放当前的 Buffer,只会释放旧的 Buffer),B Buffer 被 GPU 在使用,这就是 双缓冲机制的局限性。
Android 4.1 版本中对 Android Display 系统进行了重构,引入了三个核心元素:
在第二个 16ms 时间内,CPU 使用 C Buffer 绘图,虽然还是会多显示 A 帧一次,但是后续的显示相对双缓冲机制就顺滑多了。但是 Buffer 并不是越多越好,从上图可知,在第二个时间内,CPU 绘制的第 C 帧数据要到第四个 16ms 才能显示,这比双 Buffer 多了 16ms 的延迟。由此可见,双缓冲保证低时延,三缓冲保证稳定性。
整个流程简单来说就是 CPU/GPU 会接收到 VSYNC 信号,触发对 UI 进行渲染(每 16ms 显示一帧)。 在 16ms 内需要完成两项任务:将 UI 对象转换为一系列多边形和纹理(栅格化)和 CPU 传递处理数据到 GPU ,更详细的内容可以看这篇文章 Android的16ms和垂直同步以及三重缓存 。
了解 Android 绘制流程后,我们不难反推 Android 应用程序卡顿的原因: