共计 3048 个字符,预计需要花费 8 分钟才能阅读完成。
导语:
在测试流畅度的过程中,必不可免的要与 FPS,Jank 等指标接触,但为了加深理解,今天来简单扒一扒安卓的渲染原理;
PerfDog 使用 Jank 作为来代表游戏流畅度的指标,详情可以看
APP& 游戏需要关注 Jank 卡顿吗?
一.CPU 与 GPU 结构
现在大部分移动端都会配有 CPU(中央处理器)和 GPU(图形处理器),有的现在还有一块 NPU 用于处理智能运算。来简单看一下他们的结构;
[](https://img-blog.csdnimg.cn/2…
[
](https://img-blog.csdnimg.cn/2…
绿色的是计算单元 (ALU),
橙红色的是存储单元,
橙黄色的是控制单元。
CPU 需要很强的通用性来处理各种不同的数据类型,同时又要进行复杂的数学和逻辑运算,所以使得 CPU 的内部结构异常复杂;
CPU 被 Cache 占据了大量空间,还有很多复杂的控制逻辑和诸多优化电路,其实计算能力只是 CPU 很小的一部分,在早期的时候,CPU 除了做逻辑计算外,还负责内存管理、图形显示等操作因此在实际运算的时候性能会大打折扣,而且还不能显示复杂的图形,完全不能满足现在 3D 游戏的要求;所以 GPU 应运而生。
GPU 面对的则是类型高度统一的、相互无依赖的大规模数据和不需要被打断的纯净的计算环境,所以结构也大不相同。
GPU 采用了数量众多的计算单元和超长的流水线,但只有非常简单的控制逻辑并省去了 Cache,GPU 将计算机系统所需要的显示信息进行转换驱动,并向显示器提供行扫描信号,控制显示器的正确显示,主要负责图形显示部分的工作。
二.Android 系统绘图机制
[
](https://img-blog.csdnimg.cn/2…
现在的安卓终端通常在一个典型显示系统中首先由 CPU 发出图像绘制指令要让 GPU 去画一个样式,但 CPU 不能直接和 GPU 通信,也要遵守相应的规则,就和现在我们干什么事都要走个流程一样的嘛,不能乱套;所以 CPU 要先向 OpenGL ES 发送一些指令,表达要画一个样式,Opengl ES 是一组接口 API,** 通过这些 API 可以操作驱动,让 GPU 达到各种各样的操作;GPU 接收到这些命令,开始栅格化处理,把样式显示到屏幕中;
现在我们把应用加到显示流程里面来
[](https://img-blog.csdnimg.cn/2…
[
](https://img-blog.csdnimg.cn/2…
在 Android 应用层通过 LayoutInflater 把布局 XML 文件映射成对象加载到内存中,此时这个 UI 对象含有大小,位置啦等等信息。然后 CPU 从内存中取出这个 UI 对象,再经过运算处理成多维的矢量图形,然后交给 GPU 去栅格化成位图,显示到屏幕上;
简单介绍一下矢量图和位图
矢量图:由一个函数来描述,这个函数描述了此图如何生成
位图:由像素点矩阵来描述
Android 系统每隔 16ms 就重新绘制一次 Activity,所以要求应用必须在 16ms 内完成屏幕刷新的全部逻辑操作,这样才能达到每秒 60 帧(60FPS),然而这个每秒帧数的参数由手机硬件所决定,现在大多数手机屏幕刷新率是 60 赫兹(是每秒中的周期性变动重复次数的计量),如果超过了 16ms 就会出现所谓的丢帧(1000ms/60=16.66ms)
三. 一帧图像完整渲染过程
[
](https://img-blog.csdnimg.cn/2…
在 Android 应用程序窗口里面包含了很多视图(View)元素,这些元素是以树形结构来组织,最终构成所谓视图树的结构;
在绘制一个 Android 应用程序窗口的 UI 之前,要确定它里面的各个子 View 元素在父元素里面的大小以及位置。确定各个子 View 元素在父 View 元素里面的大小以及位置的过程又称为测量过程和布局过程。Android 应用程序窗口的 UI 渲染过程可以分为
Measure(测量)、Layout(布局)和 Draw(绘制)
三个阶段(由 ViewRootImpl 类的 performTraversals()方法发起)
测量——递归(深度优先)确定所有视图的大小(高、宽)
布局——递归(深度优先)确定所有视图的位置
绘制——在画布 canvas 上绘制应用程序窗口所有的视图
经过多次绘制后,这一帧内要显示的所有 view 都已经被绘制完毕,注意绘制 View 层次结构这些操作是在图形缓冲区中绘制完成的;
此时就要把这个图形缓冲区被交给 SurfaceFlinger 服务
SurfaceFlinger 服务概述:
[](https://img-blog.csdnimg.cn/2…
[
](https://img-blog.csdnimg.cn/2…
SurfaceFlinger 服务和其他系统服务一样是在 Android 系统的 System 进程里被启动并运行在其中的,主要负责统一管理设备中 Android 系统的帧缓冲区(Frame Buffer,简单理解为屏幕所显示出来的所有图形效果都是由它统一管理的),在 SurfaceFlinger 服务启动的过程中会自动创建两个线程:其中一个线程用于监控控制台事件,另外一个线程则用于渲染系统的 UI;
Android 应用程序为了能够将自己的 UI 绘制在系统的帧缓冲区上,就需要将 UI 数据传递 SurfaceFlinger 服务并告知自己具体的 UI 数据(例如要绘制 UI 的区域、位置等信息),
Android 应用程序与 SurfaceFlinger 服务是运行在不同的进程中,所以相互间通过 Binder 机制进行通信,
大致可以分为 3 步:
1. 首先是创建一个到 SurfaceFlinger 服务的连接,
2 通过这个连接来创建一个 Surface,
3. 请求 SurfaceFlinger 服务渲染该 Surface(在 Android 应用的每个窗口对应一个画布(Canvas),也可以理解为 Android 应用程序的一个窗口)
在 APP 层我们对于这部分的无法进行任何的优化,这是 ROOM 做的工作。
简单来说就是当 Android 应用层在图形缓冲区中绘制好 View 层次结构后,应用层通过 Binder 机制与 SurfaceFlinger 通信并借助一块匿名共享内存会把这个图形缓冲区会被交给 SurfaceFlinger 服务。因为单纯的匿名共享内存在传递多个窗口数据时缺乏有效的管理,所以匿名共享内存就被抽象为一个更上流的数据结构 SharedClient,在每个 SharedClient 中,最多有 31 个 SharedBufferStack,每个 SharedBufferStack 都对应一个 Surface 即一个窗口。
帧缓存有个地址,是在内存里。我们通过不停的向 frame buffer 中写入数据,显示控制器就自动的从 frame buffer 中取数据并显示出来。全部的图形都共享内存中同一个帧缓存。
四.VSync 机制
为了减少卡顿,Android 4.1(JB)中已经开始引入 VSync(垂直同步)机制
简单来说就是 CPU/GPU 会接收 vsync 信号,Android 系统每隔 16ms 发出 Vsync 信号,触发对 UI 进行渲染(即每 16ms 显示一帧)
在 16ms 内需要完成两项任务:将 UI 对象转换为一系列多边形和纹理(栅格化)和 CPU 传递处理数据到 GPU。
但即使引入垂直同步机制也不是非常完美,如果某些原因导致 CPU 和 GPU 渲染某一帧画面的时间超过 16ms 时,Vsync 垂直同步机制会让硬件显示器等待,直到 GPU 完成栅格化操作,这就直接导致这一帧画面多停留了 16ms 甚至更长时间,让用户看起来画面停顿。