美文网首页Android
Android 绘制与 16ms 不得不说的故事

Android 绘制与 16ms 不得不说的故事

作者: tandeneck | 来源:发表于2020-08-26 22:04 被阅读0次

    刷新率、帧率

    刷新率:每秒屏幕刷新次数。
    帧率:GPU 在一秒内绘制的帧数。
    虽然现在有的厂商推出了高刷新率的手机,但是主流的还是 60Hz,即1秒显示60帧,1000ms / 60 frames ≈ 16.67 ms/frames,为了保证 App 的流畅度,我们应该尽量让每帧的绘制时间不超过 16ms。

    Android 系统显示原理

    Android 的显示过程可以简单概括为:应用程序把经过 measure(测量)、layout(布局)、draw(绘制)后的 surface 缓存数据,通过 SurfaceFlinger 把数据渲染到显示屏幕上,通过 Android 的刷新机制来刷新数据。换言之,应用层负责绘制,系统层负责渲染,通过进程间通信把应用层需要绘制的数据传递到系统层服务,系统层通过刷新机制把数据更新到屏幕上。

    以下是有关概念的解释:

    • Surface,surface持有用于最终显示在屏幕上的像素数据,一个 window 对应着一个 Surface,Android 中 每个 view 都会绘制到 Surface 上,其中包含了两个(小于4.1版本)或者三个(4.1及以上版本)缓冲区中。
    • Window,window 实际上是 View 的直接管理者,每个 window 拥有一个 surface,它的具体实现是 PhoneWindow,WindowManager 是外界访问 window 的入口。
    • Canvas ,Canvas 是 Surface 绘图时返回的一个接口,并提供丰富的绘图的 API,用来进行实际的绘图操作。
    应用层

    在 Android 中每个 view 都会经过 measure 和 layout 来确定其所在的大小和位置,然后绘制到 surface (缓冲区上),绘制是由 ViewRootImpl 类中 performTraversals() 方法发起的。

    Android支持两种绘制方式:\color{red}{软件绘制}\color{red}{硬件绘制}。硬件极速从 Android 3.0 开始支持,它在 UI 显示和绘制效率方面远高于软件绘制,但是它的也有缺点:

    • 耗电,GPU 功耗高于 CPU。
    • 兼容性,不兼容某些接口和方法。
    • 内存占用大。
    系统层

    经过多次绘制后,要显示的 view 相关的数据存储(如大小和位置)在 Surface 的缓冲区中,接下来渲染操作交由系统进程中的 SurfaceFlinger 服务来完成,这是一个 IPC(进程间通信)过程。SurfaceFlinger 的主要工作流程如下:

    • 响应客户端事件,创建 Layer 与客户端的 Surface 建立连接。
    • 接收客户端数据和属性,修改 Layer 属性,如尺寸、颜色、透明度等。
    • 将创建的 Layer 内容刷新到屏幕上。
    • 维持 Layer 的序列,并对 Layer 最终输出做出裁剪计算。

    当 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 系统进行了重构,引入了三个核心元素:

    • \color{red}{VSYNC(垂直同步)} 。VSYNC 是 Vertical Synchronization 的缩写,可以认为是一种定时中断。
    • \color{red}{Choreography(编舞者)} 。 Choreographer 起调度的作用,将绘制工作统一到 VSYNC 的某个时间点上,使应用的绘制工作有序,具体来说是当收到 VSYNC 信号时,调用用户设置的回调函数,回调类型的优先级从高到低为 ALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL。
    • \color{red}{TripleBuffer} 。Triple Buffer 的引入是为了解决上文提到的 CPU 没有 buffer 可用的窘境。如下图所示:

    在第二个 16ms 时间内,CPU 使用 C Buffer 绘图,虽然还是会多显示 A 帧一次,但是后续的显示相对双缓冲机制就顺滑多了。但是 Buffer 并不是越多越好,从上图可知,在第二个时间内,CPU 绘制的第 C 帧数据要到第四个 16ms 才能显示,这比双 Buffer 多了 16ms 的延迟。由此可见,双缓冲保证低时延,三缓冲保证稳定性。

    整个流程简单来说就是 CPU/GPU 会接收到 VSYNC 信号,触发对 UI 进行渲染(每 16ms 显示一帧)。在 16ms 内需要完成两项任务:将 UI 对象转换为一系列多边形和纹理(栅格化)和 CPU 传递处理数据到 GPU,更详细的内容可以看这篇文章Android的16ms和垂直同步以及三重缓存

    拓展

    了解 Android 绘制流程后,我们不难反推 Android 应用程序卡顿的原因:

    • 绘制任务太重,绘制一帧的时间耗时太长,超过了 16ms。常见的场景是布局层级嵌套过多、过度绘制等。
    • 主线程阻塞,导致 VSYNC 信号到来时还没有准备好数据导致丢帧。

    参考

    相关文章

      网友评论

        本文标题:Android 绘制与 16ms 不得不说的故事

        本文链接:https://www.haomeiwen.com/subject/hestsktx.html