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 多屏调度中的合成策略

最后,我想聊聊实际项目中怎么选合成策略。这不是理论问题,是工程问题。

常见的合成策略有三种:

  1. 全GPU合成:所有图层用GPU合成,灵活性高,但功耗大
  2. 全HWC合成:所有图层交给硬件合成器,功耗低,但图层数量受限
  3. 混合合成:部分图层GPU合成,部分HWC合成,取长补短

我建议你在座舱项目中优先考虑混合合成。为什么?因为座舱的图层数量通常不多(导航、仪表、媒体播放器),但每个图层都很重要。混合合成可以在保证画质的同时降低功耗。

实战建议:如果你用SurfaceFlinger,可以通过dumpsys SurfaceFlinger查看当前合成策略。如果发现某个图层被强制GPU合成,检查一下它的格式是不是HWC不支持的。我曾经遇到过因为图层格式是RGBA_8888而不是RGBX_8888,导致HWC拒绝合成的情况。改一下格式就好了。

嗯,这一章的内容差不多就这些。总结一下:SurfaceFlinger和Wayland合成器是多屏调度的软件核心,虚拟显示是扩展多屏能力的关键技术。理解它们的原理,你才能在设计座舱系统时做出正确的架构决策。

下一章,我们会深入具体的多屏调度实战案例,包括双屏异显、三屏联动、以及虚拟显示在投屏中的应用。到时候见。