一、基础介绍
SurfaceFlinger 是 Android 系统中的一个关键组件,负责管理屏幕显示的合成和渲染。
- 服务角色:SurfaceFlinger 作为一个系统服务独立运行,它不依赖于任何应用程序进程,而是由系统启动并持续运行。
- 窗口管理:它管理着系统中所有窗口的层次结构,包括应用程序窗口、状态栏、导航栏等,并根据ZOrder(深度顺序)决定哪些窗口应该在前面显示。
- 合成操作:SurfaceFlinger 接收各个窗口的 Surface(图形缓冲区),这些 Surface 可能来自不同的应用程序或系统服务。根据每个 Surface 的透明度、大小、位置等属性,SurfaceFlinger 计算它们在最终显示图像中的位置和效果。
- 硬件加速:SurfaceFlinger 利用硬件加速功能,如 GPU,来高效地执行合成操作。它可以与 HWComposer(硬件作曲家)协同工作,优化显示性能和功耗。
- 帧缓冲区操作:合成后的图像被写入到帧缓冲区,然后由显示硬件读取并呈现到屏幕上。
- 多显示器支持:SurfaceFlinger 还支持多显示器配置,能够将内容正确地输出到连接到设备的多个显示设备上。
- 低延迟渲染:为了实现流畅的用户体验,SurfaceFlinger 致力于减少帧之间的延迟,确保以 60Hz 或其他刷新率连续显示内容。
- 与其他组件的交互:与 WMS(Window Manager Service)协作,处理窗口的布局和可见性。与 Content Providers、View System 等其他 Android 组件进行通信,获取和更新显示内容。
- 权限控制:SurfaceFlinger 还负责权限控制,确保只有授权的进程可以访问和修改显示内容。
通过上述功能,SurfaceFlinger 在 Android 系统中扮演着至关重要的角色,确保了用户界面的正确显示和高效的渲染。
二、系统渲染流程
1、SurfaceFlinger
SurfaceFlinger 是整个 Android 系统渲染的核心进程。所有应用的渲染逻辑最终都会来到 SurfaceFlinger 中进行处理,最终会把处理后的图像数据交给 CPU 或者 GPU 进行绘制。
在这个过程中 SurfaceFlinger 并非担当渲染的角色,而是作为图元抛射机一样,把所有应用进程传递过来的图元数据加工处理后,交给 CPU 和 GPU 做真正的绘制。
2、Surface
在每一个应用中都以 Surface 作为一个图元传递单元,向 SurfaceFlinger 这个服务端传递图元数据。
Surface 与 SurfaceFlinger 交互流程如下图所示:
3、设计思想
SurfaceFlinger 是以生产者以及消费者为核心设计思想,把每一个应用进程作为生产者生产图元保存到 SurfaceFlinger 的图元队列中,SurfaceFlinger 则作为消费者依照一定的规则把生产者存放到 SurfaceFlinger 中的队列一一处理。
SurfaceFlinger 处理 Surface 流程如下:
4、共享内存
为了能够跨进程的传输大容量的图元数据,使用了匿名共享内存内存作为工具把图元数据都输送到 SurfaceFlinger 中处理。
我们要知道从应用进程把图元数据传输到 SurfaceFlinger 进程中处理,就需要跨进程通信,这里我们能想到的就是 socket通信、Binder 通信以及共享内存了。对于 Ashmem 匿名共享内存的选择,后面我们详细讲解。
5、时间钟
SurfaceFlinger 底层有一个时间钟在不断的循环,或从硬件中断发出,或从软件模拟发出计时唤起,每隔一段时间都会获取 SurfaceFlinger 中的图元队列通过 CPU/GPU 绘制在屏幕。
这个时间钟很符合 Android 系统的设计情况,除了需要 Android 应用有办法通知 SurfaceFlinger 需要渲染的模式,也需要 SurfaceFlinger 在不断的处理图元数据并绘制到屏幕的同时回调信息给自身。
其中 EventThread 扮演一个极其重要的角色,在 SurfaceFlinger 中设计大致如下:
三、缓冲原理
在了解缓冲原理前,我们先来开一个比较重要的概念——垂直同步信号(VSync)。
1、垂直同步信号
在 SurfaceFlinger 中,垂直同步信号(Vertical Synchronization, VSync)起着核心作用,确保图形渲染和屏幕显示的同步,避免画面撕裂现象并提高视觉流畅性。
- 作用原理:VSync 是一种同步机制,与显示器的刷新周期对齐,指示何时开始新的一帧渲染最合适。每当显示器完成一次刷新周期,硬件会产生一个 VSync 信号,通知系统现在可以安全地开始绘制下一帧图像。
- 硬件与软件的结合:虽然早期的 SurfaceFlinger 可能依赖于软件模拟 VSync,现代 Android 系统通常利用硬件提供的精确 VSync 信号,以减少误差和提升效率。
- 分发与接收:SurfaceFlinger 负责接收硬件产生的 VSync 信号,并将其分发给系统中的渲染器,包括 App 的 UI 线程和 SurfaceFlinger 自身。onVSyncReceived 方法会在接收到 VSync 信号时被调用,该方法内部会触发一系列的渲染调度操作。
- 同步渲染:应用程序通过 Choreographer(编舞者)类监听 VSync 信号,确保 UI 绘制与屏幕刷新同步。这使得 AppUI 和 SurfaceFlinger 都能按照硬件 VSync 的节奏进行工作,从而保证动画和滚动等操作的平滑性。
- 减少延迟与提高效率:通过精确的 VSync 同步,可以减少由于无序渲染导致的额外缓冲需求,降低输入到显示的延迟。VSync 还帮助优化 CPU 和 GPU 的利用率,避免过度渲染或空闲等待,进而节省电池。
- 可调节性:在某些场景下,开发者可以通过调整 Choreographer 的行为来适应特定的渲染需求,比如三重缓冲策略的调整。
- 异常处理与校准:SurfaceFlinger 还涉及处理 VSync 信号的异常情况,比如信号丢失或频率不匹配,以维持系统的稳定性和显示质量。
综上所述,VSync 在 SurfaceFlinger 中是确保图形显示流畅和高效的基石,通过精确的时间控制,协调系统资源,提供最佳的用户体验。
2、双缓冲
在 SurfaceFlinger 中,双缓冲是一种优化显示性能的技术,用于减少屏幕闪烁并提高动画的平滑性。
- 缓冲区管理:双缓冲涉及到两个独立的帧缓冲区(front buffer 和 back buffer)。前缓冲区是当前显示在屏幕上的图像,而后缓冲区则用于绘制新的内容。
- 绘制过程:应用程序在后台缓冲区完成绘制操作,不会影响到正在显示的图像。当绘制完成后,SurfaceFlinger 会收到 VSync 信号,此时可以安全地交换前后缓冲区。
- 缓冲区交换:在 VSync 时刻,SurfaceFlinger 将后缓冲区的内容复制到前缓冲区,这个过程称为"翻页"或"PageFlip"。这个操作通常是原子性的,确保在屏幕刷新时看到的是完整的一帧,而不是部分更新的内容。
- 减少闪烁:因为用户看到的始终是已经完成绘制的图像,而不是正在绘制的过程,所以避免了因多次绘制导致的闪烁现象。
- 提高效率:应用程序可以同时在后缓冲区进行下一帧的绘制,而前一帧的显示不会受到影响,这提高了渲染效率。
简单来说,双缓冲就是渲染第一帧的同时已经在绘制第二帧的内容,等到第二帧绘制完毕后就显示出来。这么做的好处很明显,如果一帧画完,才开始画下一帧,势必有一个计算的过程导致 ui 交互迟缓。
双缓冲是 SurfaceFlinger 在处理多个窗口和动画时的关键技术,它确保了 Android 系统的视觉流畅性和稳定性。通过与 VSync 信号的配合,双缓冲有效地减少了画面撕裂和不连续的视觉效果,提升了用户体验。
通过这种方式显示前一帧的时候提前绘制好下一帧图元,放在背后等待时机交换,这样就能从感官上流畅不少。
这么做的效果十分显示,但是怎么找到一个合适的时机进行交换前后两帧这是一个问题,如果按照屏幕刷新频率来,一般按照通用屏幕刷新 60fps 也就是约 16ms 刷新一次即可。但是我们深入思考一下,其实这个过程中有两个变量,一个是绘图速度,一个是显示速度。就算是绘图速度中也有分 CPU 和 GPU 的绘制速度。
这里我看一下当年 google 在“黄油计划”示意图。
无缓冲流程
理想中的情况就是上图,在显示第 0 帧的时候,CPU/GPU 合成绘制完成第 1 帧在 16ms 内,当 vsync 信号来了,就把第 1 帧交换到显示屏显示。
但是很可能出现下面这种情况,CPU 因为繁忙来不及,显示完第一帧的时候,还没空渲染第二帧,就算 SurfaceFlinger 接受到了 Vsync 的信号,也只能拿出已经渲染好的第一帧显示在屏幕上。这样就重复显示了第一帧,Google 开发团队称这种为 jank。
能看到显示第一帧因为第二帧没准备好,只能重复显示第一帧了。再来看看双缓冲的工作原理流程。
双缓冲流程
可以看到此时就不是简单的第一第二帧,而是分为 A 缓冲,B 缓冲。能看到在正常情况下,先显示 A 缓冲的内容,同时准备B缓冲,当一切正常的时候,B 缓冲应该在下一个 vsync 来之前准备好,一旦 vsync 到来则显示 B 缓冲,A 缓冲回到后台继续绘制。
那么这种方式一旦遇到 jank 会是什么样的一个情况。
如果是双缓冲好像没有问题,但是一旦出现 jank 了之后,之后显示屏就会不断的出现 jank。如果缓冲 A 在显示,而 B 准备的时间超过 16ms,就会导致 A 缓冲区重复显示,而当 B 显示的时候,A 也很可能准备时间不足 16ms 导致无法绘制完成,只能重复显示 B 缓冲的内容。
这种方式更加的危险,为了解决这个问题,Google 引入三重缓冲。
3、三重缓冲
在 SurfaceFlinger 中,三重缓冲(Triple Buffering)是双缓冲的一个扩展,旨在进一步减少画面撕裂和提高渲染性能,特别是在高帧率和垂直同步(V-Sync)启用时。
- 缓冲区数量:与双缓冲的两个缓冲区不同,三重缓冲使用三个独立的帧缓冲区。当前显示的前缓冲区、一个用于绘制的新缓冲区(后缓冲区)以及一个额外的中间缓冲区。
- 渲染流程:应用程序在中间缓冲区完成新帧的渲染,同时前缓冲区正在屏幕上显示。当前帧绘制完成后,SurfaceFlinger 等待下一个 VSync 信号,然后将中间缓冲区的内容移动到后缓冲区。
- 缓冲区交换:在下一个 VSync 到来时,SurfaceFlinger 将后缓冲区的内容复制到前缓冲区,同时释放中间缓冲区供应用程序绘制下一帧。
- 这样,应用程序可以在前一帧显示的同时开始绘制下一帧,而不是等待前一帧完全呈现后再开始。
- 减少延迟:三重缓冲减少了等待 VSync 信号的空闲时间,因为总有一个缓冲区可供应用程序使用,从而降低了帧间延迟。
- 性能提升:在某些情况下,尤其是在高帧率下,三重缓冲可以减少因等待 VSync 而引起的帧率下降,从而提供更稳定的帧率表现。
- 权衡:尽管三重缓冲可以提高性能,但它也增加了内存占用,因为需要存储额外的缓冲区。此外,对于某些设备或应用场景,双缓冲可能已经足够,而且更节省资源。
- 适用场景:三重缓冲通常在需要高性能图形渲染、高刷新率显示器或者需要减少 V-Sync 带来的延迟时采用。
三重缓冲是 SurfaceFlinger 为了优化高动态场景和减少延迟而采用的一种策略,它在现代游戏和高性能应用中尤其有用,能够提供更加流畅的视觉体验。
三重缓冲处理 jank 的原理流程图:
可以看到为了避免后面连锁式的错误,引入三重缓冲就为了让空闲出来的等待时间,能够做更多的事情。就如同双缓冲遇到 jank 之后,一旦 B 缓冲 CPU+GPU 的时间超过了下一个 vsync 的时间,能够发现其实 CPU 和 GPU 有一段时间都没有事情做,光等待下一次 Vsync 的到来,才会导致整个系统后面的绘制出现连锁式的出现 jank。
而三缓冲的出现,在重复显示 A 缓冲区的时候,CPU 不会光等待而是会准备 C 缓冲区的图元,之后就能把 C 缓冲区接上。这就是 Google 所说的三重缓冲区的来源,不过绝大多数情况下的缓冲策略都是由 SurfaceFlinger 系统自己决定的。实际上这种方式也可以用到音视频的编写优化,里面常用的缓冲区设计和这里也有异曲同工之处。
有了缓冲原理的介绍,这里我们再来回顾一下上面的时间钟。其实每一次 Vsync 从硬件/软件过来的时候,Dispsync 都会尝试着通知 SurfaceFlinger 和APP,这是完全没有问题,而后面那个 Phase 相位又有什么作用?这就是系统的设计的巧妙,我们如果同时把信号通知同时告诉 APP 和 SurfaceFlinger 会导致什么结果?
如果此时 APP 后返回了图元,但是 SurfaceFlinger 已经执行了刷新合成绘制行为(很有可能,因为 APP 到 SurfaceFlinger 传输图元速度必定比 SurfaceFlinger 自己通知自己慢),此时就会导致类似 jank 的问题,导致下一个 vsync 还是显示当前帧数,因此需要如下一个时间差,先通知 APP 后通知 SurfaceFlinger,如下图:
加上这个理解就能明白上面 Phase 相位处理的初衷了。
总结
对于系统渲染流程的理解是指导 SurfaceFlinger 设计的核心思想,从 Android 4.1 一直到 Android 9.0 都没有太大的变化。只要抓住这五个核心思想,我们阅读 SurfaceFlinger 的难度就会下降不少。
下面看一下 SurfaceFlinger 的体系和 Skia 以及 View的绘制流程的关系。
- framework 面向开发者所有的 View 是便于开发的控件,里面仅仅只是提供了当前 View 各种属性以及功能。
- 而 Android 底层的 Skia 是 Android 对于屏幕上的画笔,经过 View 绘制流程的 onDraw 方法回调,把需要绘制的东西通过 Skia 绘制成像素图元保存起来
- SurfaceFlinger 则是最后接受 Skia 的绘制结果,最后绘制到屏幕上。
所以说,Skia 是 Android 渲染核心,但是最终还是需要 Skia 和系统所提供起来,才是一个 Android 完整渲染体系。经过这一层层的屏蔽,让开发者不需要对 Android 底层的渲染体系有任何理解,也能绘制出不错的效果。
最后会把绘制结果传输到屏幕中。