4、SurfaceFlinger与多屏:Android/Wayland合成架构、多Display支持、虚拟显示(Virtual Display)技术
好,咱们进入第四章。这一章我打算聊聊多屏调度里最核心的软件层——显示合成架构。
你想想看,硬件上我们有了多个显示控制器,有了G2D、GPU这些加速单元。但谁来管这些资源?谁来告诉系统「这块屏幕该显示什么,那块屏幕又该显示什么」?
答案就是合成器。在Android这边是SurfaceFlinger,在Linux/Wayland那边是Weston或者类似的合成器。说白了,它们就是显示系统的「总导演」。
4.1 SurfaceFlinger:Android显示系统的「大脑」
SurfaceFlinger这个名字,我刚开始接触时觉得挺拗口。后来理解了——它就是把各个应用的Surface「Fling」到一起,然后输出到屏幕。
它的核心职责其实就三件事:
- 接收图层:每个应用窗口都是一个图层(Layer),SurfaceFlinger负责收集它们
- 合成输出:用GPU或者硬件合成器(HWC)把这些图层叠在一起
- 送显刷新:把合成好的画面推给显示硬件
嗯,这里有个关键点——SurfaceFlinger本身不直接画图。它是个「调度员」,不是「画家」。真正的绘制工作由应用的RenderThread或者Skia/OpenGL完成,画好了把Buffer交给SurfaceFlinger。
核心架构图(逻辑示意)
App1 (Surface) ──┐
App2 (Surface) ──┤
App3 (Surface) ──┼──> SurfaceFlinger ──> HWC ──> Display
│ │
│ Virtual Display (录屏/投屏)
└─────────┘
我在项目中遇到过一个问题:某个定制ROM上,第三方应用频繁闪黑屏。查了两天才发现,是SurfaceFlinger的BufferQueue满了,应用提交的Buffer被丢弃了。后来调整了BufferQueue的深度,问题解决。这种坑,你不做底层真的很难碰到。
4.2 多Display支持:不止是「扩展屏幕」
多屏调度,SurfaceFlinger怎么支持多个Display?
其实Android从很早开始就支持多Display了。每个Display对应一个独立的合成通道。你可以理解为:SurfaceFlinger内部维护了一个Display列表,每个Display有自己的合成上下文。
常见的多Display场景:
- 主屏 + 副屏:仪表盘显示导航,中控显示娱乐
- 主屏 + HUD:抬头显示和主屏内容不同
- 后排娱乐屏:独立显示视频内容
每个Display可以有自己的分辨率、刷新率、旋转角度。SurfaceFlinger会为每个Display单独做合成。
个人经验:我建议你在设计多屏方案时,先搞清楚「哪个Display是主屏」。主屏通常负责系统UI、状态栏、导航栏。副屏可以只显示特定应用。这个区分很重要,因为系统事件(比如来电通知)默认只投递到主屏。
我记得有一次做车机项目,客户要求副屏独立播放视频,但声音要从主屏出。这就涉及到Display和Audio的路由问题了。SurfaceFlinger只管画面,声音得另外处理。嗯,这里要注意,多屏不只是画面的事。
4.3 虚拟显示(Virtual Display):看不见的「屏幕」
虚拟显示这个概念,说实话我第一次接触时觉得挺抽象的。明明没有物理屏幕,为什么要创建一个Display?
后来做录屏功能时才明白——虚拟显示就是一个「看不见的屏幕」。它不连接任何物理硬件,但SurfaceFlinger会像对待真实屏幕一样合成画面,然后把合成结果输出到一个Buffer里,而不是显示器。
虚拟显示的核心用途:
- 屏幕录制:把屏幕内容抓取到MediaCodec编码
- 投屏/镜像:把画面推送到WiFi Display或有线投屏
- 多用户隔离:每个用户有自己的虚拟显示,互不干扰
- 测试/模拟:在没有物理屏幕的环境下验证UI
创建虚拟显示的代码片段(Android Framework层)
// 创建一个虚拟显示,宽1920,高1080,DPI 320
DisplayManager dm = context.getSystemService(DisplayManager.class);
VirtualDisplay virtualDisplay = dm.createVirtualDisplay(
"录屏虚拟屏", // 名称
1920, 1080, // 宽高
320, // DPI
surface, // 输出目标(Surface)
DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
);
你想想看,虚拟显示的本质是什么?其实就是把「合成」和「显示」解耦了。SurfaceFlinger照常合成,但输出目标从物理屏幕换成了一个内存Buffer。这个Buffer你可以拿去编码、传输、或者做任何处理。
避坑指南:我曾经在虚拟显示上栽过跟头。当时做双屏异显,副屏用了虚拟显示方案。结果发现虚拟显示的刷新率不受Vsync控制,导致画面撕裂。后来查资料才知道,虚拟显示默认没有Vsync信号,需要手动设置刷新率或者用Choreographer同步。这个坑,我建议你提前留意。
4.4 Wayland合成架构:与Android的异同
聊完Android,咱们看看Linux这边的Wayland。Wayland的合成器(比如Weston、GNOME的Mutter)和SurfaceFlinger思路很像,但设计哲学不同。
| 对比维度 | SurfaceFlinger (Android) | Wayland Compositor |
|---|---|---|
| 合成方式 | GPU合成 + HWC硬件合成 | GPU合成为主,部分支持硬件合成 |
| 多Display | 原生支持,每个Display独立合成 | 原生支持,通过wl_output全局对象 |
| 虚拟显示 | VirtualDisplay API,成熟稳定 | 通过虚拟输出协议,生态较新 |
| Buffer管理 | Gralloc + BufferQueue | DMA-BUF + wl_buffer |
| Vsync机制 | HW Vsync + 软件Vsync | DRM Vblank事件驱动 |
说白了,两者都是「合成器+显示输出」的架构。但Android的SurfaceFlinger更封闭、更定制化,而Wayland更开放、更遵循Linux标准。
我个人习惯在做座舱方案时,如果底层是Linux,优先考虑Wayland。因为它的协议更清晰,调试工具(比如wayland-info)也更方便。但如果要兼容Android应用生态,那还是得用SurfaceFlinger。
4.5 多屏调度中的合成策略
最后,我想聊聊实际项目中怎么选合成策略。这不是理论问题,是工程问题。
常见的合成策略有三种:
- 全GPU合成:所有图层用GPU合成,灵活性高,但功耗大
- 全HWC合成:所有图层交给硬件合成器,功耗低,但图层数量受限
- 混合合成:部分图层GPU合成,部分HWC合成,取长补短
我建议你在座舱项目中优先考虑混合合成。为什么?因为座舱的图层数量通常不多(导航、仪表、媒体播放器),但每个图层都很重要。混合合成可以在保证画质的同时降低功耗。
实战建议:如果你用SurfaceFlinger,可以通过dumpsys SurfaceFlinger查看当前合成策略。如果发现某个图层被强制GPU合成,检查一下它的格式是不是HWC不支持的。我曾经遇到过因为图层格式是RGBA_8888而不是RGBX_8888,导致HWC拒绝合成的情况。改一下格式就好了。
嗯,这一章的内容差不多就这些。总结一下:SurfaceFlinger和Wayland合成器是多屏调度的软件核心,虚拟显示是扩展多屏能力的关键技术。理解它们的原理,你才能在设计座舱系统时做出正确的架构决策。
下一章,我们会深入具体的多屏调度实战案例,包括双屏异显、三屏联动、以及虚拟显示在投屏中的应用。到时候见。