7、显示与多屏系统:多屏显示架构、SurfaceFlinger 与 WindowManager、车载多屏适配策略

车载系统跟手机最大的区别在哪?我个人觉得,就是屏幕数量。手机再大也就一块屏,车里呢?中控、仪表、副驾娱乐、后排、HUD……少则两三块,多则七八块。这么多屏幕要协同工作,显示架构就成了核心中的核心。

今天我们就来聊聊 Android Automotive OS 的多屏系统。我会从底层架构讲起,再到 SurfaceFlinger 和 WindowManager 怎么配合,最后给出实际项目中的适配策略。嗯,这部分内容我踩过不少坑,希望能帮你少走弯路。

7.1 多屏显示架构:从硬件到应用

先看整体架构。Android 的显示系统分三层:硬件层系统服务层应用层

核心要点:车载多屏不是简单的「复制粘贴」,而是「独立渲染、独立合成、独立输出」。每块屏幕都有自己的显示管道。

我画了一张架构图,帮你理清关系:

Android Automotive OS 多屏显示架构 应用层 (Application Layer) 中控应用 | 仪表应用 | 副驾娱乐应用 | HUD 应用 每个应用通过 WindowManager 指定目标 DisplayId 系统服务层 (System Services) WindowManagerService (WMS) — 窗口管理、焦点、Z-order DisplayManagerService (DMS) — 物理/虚拟显示器管理 SurfaceFlinger — 图层合成、硬件合成器 HWC 硬件层 (Hardware Layer) GPU (渲染) → HWC (合成) → Display Controller (输出) 每块屏幕对应独立的显示控制器和物理接口 图:Android Automotive OS 多屏显示三层架构

说白了,应用层只管「画」,系统服务层管「怎么摆、摆在哪」,硬件层管「最终输出」。这三层各司其职,缺一不可。

7.2 SurfaceFlinger:图层合成的「总导演」

SurfaceFlinger 是 Android 显示系统的核心。它的工作很简单——把所有应用的图层合成一帧,然后交给屏幕显示。但在多屏场景下,事情就复杂了。

SurfaceFlinger 的多屏处理逻辑:

  • 每个物理显示器对应一个 DisplayDevice 对象
  • 每个 DisplayDevice 有自己的合成链(Composition Chain)
  • SurfaceFlinger 按 DisplayId 区分不同屏幕的图层
  • 硬件合成器(HWC)负责将图层合成到对应屏幕

我的经验:曾经有个项目,仪表屏和中控屏共用同一个 HWC 实例,结果仪表刷新率被中控拖累。后来改成独立 HWC 实例,问题解决。记住——多屏场景下,HWC 的隔离性非常重要

SurfaceFlinger 的合成策略有三种:

策略 说明 适用场景
GPU 合成 所有图层由 GPU 合成,再输出到屏幕 图层数量少、性能要求不高的场景
HWC 合成 硬件合成器直接合成图层,不经过 GPU 性能敏感场景(如仪表屏 60fps)
混合合成 部分图层 GPU 合成,部分 HWC 合成 复杂场景(如中控屏有视频叠加层)

嗯,这里要注意:HWC 合成虽然性能好,但灵活性差。如果图层有透明叠加、旋转等复杂变换,HWC 可能不支持,会回退到 GPU 合成。我在项目中就遇到过——仪表屏的指针动画用了旋转属性,结果 HWC 不支持,帧率直接掉了一半。

7.3 WindowManager:窗口的「调度员」

WindowManager 负责管理窗口的创建、显示、焦点、Z-order 等。在多屏场景下,它需要知道「这个窗口属于哪块屏幕」。

关键概念:DisplayId

每个窗口都有一个 DisplayId,标识它属于哪块屏幕。应用可以通过 Context.createDisplayContext() 获取指定屏幕的上下文。

// 获取指定 Display 的上下文
Display display = displayManager.getDisplay(displayId);
Context displayContext = context.createDisplayContext(display);

// 在这个上下文中创建窗口
LayoutInflater inflater = LayoutInflater.from(displayContext);
View view = inflater.inflate(R.layout.dashboard_layout, null);

// 或者直接指定 WindowManager 的 Display
WindowManager wm = (WindowManager) displayContext.getSystemService(Context.WINDOW_SERVICE);
wm.addView(view, layoutParams);

避坑指南:我曾经犯过一个错误——直接用主屏幕的 WindowManager 去添加仪表屏的窗口,结果窗口显示在了中控屏上。记住:每个屏幕必须使用对应的 WindowManager 实例

多屏焦点管理

多屏场景下,焦点管理是个大问题。用户可能同时操作中控屏和副驾屏,系统需要支持「多点触控 + 多焦点」。

  • Android 10 开始支持 多焦点模式(Multi-Focus)
  • 每个 Display 可以有自己的焦点窗口
  • 输入事件按 DisplayId 分发到对应屏幕

说白了,就是每块屏幕独立管理自己的焦点。中控屏的焦点窗口是导航应用,副驾屏的焦点窗口是视频播放器,互不干扰。

7.4 车载多屏适配策略:实战经验

理论讲完了,来点实际的。车载多屏适配,我总结了几个核心策略:

7.4.1 策略一:按屏幕类型划分职责

不同屏幕的用途不同,适配策略也不同:

屏幕类型 典型用途 适配策略
仪表屏 车速、转速、警示灯 专用应用,固定布局,高刷新率
中控屏 导航、音乐、设置 标准 Android 应用,支持多窗口
副驾屏 视频、游戏、娱乐 独立应用,支持触控和手势
HUD 导航提示、车速 轻量级应用,只显示关键信息

7.4.2 策略二:使用 DisplayManager 管理显示器

DisplayManager 是管理显示器的核心 API。你可以通过它获取所有显示器、监听显示器插拔事件。

DisplayManager dm = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);

// 获取所有显示器
Display[] displays = dm.getDisplays();

// 监听显示器变化
dm.registerDisplayListener(new DisplayManager.DisplayListener() {
    @Override
    public void onDisplayAdded(int displayId) {
        // 新屏幕接入,比如副驾屏启动
    }

    @Override
    public void onDisplayRemoved(int displayId) {
        // 屏幕移除,比如副驾屏关闭
    }

    @Override
    public void onDisplayChanged(int displayId) {
        // 屏幕参数变化,比如分辨率改变
    }
}, null);

核心原则:不要硬编码 DisplayId。不同车型的屏幕配置可能不同,一定要通过 DisplayManager 动态获取。

7.4.3 策略三:窗口策略与 Z-order 管理

多屏场景下,Z-order 管理比单屏复杂得多。每个屏幕有自己的 Z-order 栈,但系统需要全局管理。

我建议的做法:

  • 每个屏幕维护独立的 Z-order 栈
  • 系统级窗口(如警告弹窗)可以跨屏幕显示
  • 使用 WindowManager.LayoutParams.type 控制窗口层级
// 创建系统级警告窗口,可以跨屏幕显示
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
    WindowManager.LayoutParams.WRAP_CONTENT,
    WindowManager.LayoutParams.WRAP_CONTENT,
    WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,  // 系统级类型
    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
    PixelFormat.TRANSLUCENT
);

// 指定显示在哪个屏幕
params.displayId = displayId;

我的习惯:在车载项目中,我会把警告窗口的 type 设为 TYPE_SYSTEM_ALERT,这样即使某个应用崩溃了,警告窗口依然能显示。安全相关的窗口,优先级一定要高。

7.4.4 策略四:性能优化——减少合成开销

多屏意味着更多的图层、更多的合成工作。性能优化是关键。

几个实用技巧:

  • 减少图层数量:每块屏幕的图层尽量控制在 5 层以内
  • 使用硬件图层:视频播放使用 SurfaceView,它有自己的独立图层
  • 避免透明叠加:透明图层会增加合成复杂度
  • 合理设置刷新率:仪表屏 60fps,副驾屏 30fps 就够了

我曾经优化过一个项目——中控屏有 12 层图层,帧率只有 20fps。后来把不必要的图层合并,减少到 5 层,帧率直接升到 55fps。你想想看,图层数量对性能的影响有多大。

7.4.5 策略五:测试与调试

多屏调试比单屏麻烦得多。我常用的工具:

  • dumpsys display:查看所有显示器的状态
  • dumpsys window:查看窗口信息,包括 DisplayId
  • dumpsys SurfaceFlinger:查看图层合成信息
  • 开发者选项中的「模拟辅助显示设备」:可以在模拟器中测试多屏
# 查看所有显示器
adb shell dumpsys display | grep "DisplayDeviceInfo"

# 查看窗口信息
adb shell dumpsys window | grep -A 10 "Display"

# 查看 SurfaceFlinger 图层
adb shell dumpsys SurfaceFlinger --list

避坑指南:我曾经在实车上调试多屏,发现副驾屏一直黑屏。查了半天,原来是 DisplayManager 没有正确注册监听器。记住——多屏调试时,先确认物理连接,再确认软件配置

7.5 总结

多屏显示是车载系统的核心能力,也是难点所在。从 SurfaceFlinger 的图层合成,到 WindowManager 的窗口调度,再到具体的适配策略,每一步都需要仔细设计。

我个人觉得,最重要的三点:

  1. 理解架构:应用层、系统服务层、硬件层各司其职
  2. 动态适配:不要硬编码 DisplayId,通过 DisplayManager 动态管理
  3. 性能优先:减少图层数量,合理使用 HWC 合成

多屏系统就像一场交响乐,每个乐器(屏幕)都有自己的旋律,但需要指挥(SurfaceFlinger + WindowManager)统一协调。希望今天的分享能帮你更好地理解这场「交响乐」的运作方式。


公众号:蓝海资料掘金营,微信deep3321