7、显示与多屏系统:多屏显示架构、SurfaceFlinger 与 WindowManager、车载多屏适配策略
车载系统跟手机最大的区别在哪?我个人觉得,就是屏幕数量。手机再大也就一块屏,车里呢?中控、仪表、副驾娱乐、后排、HUD……少则两三块,多则七八块。这么多屏幕要协同工作,显示架构就成了核心中的核心。
今天我们就来聊聊 Android Automotive OS 的多屏系统。我会从底层架构讲起,再到 SurfaceFlinger 和 WindowManager 怎么配合,最后给出实际项目中的适配策略。嗯,这部分内容我踩过不少坑,希望能帮你少走弯路。
7.1 多屏显示架构:从硬件到应用
先看整体架构。Android 的显示系统分三层:硬件层、系统服务层、应用层。
核心要点:车载多屏不是简单的「复制粘贴」,而是「独立渲染、独立合成、独立输出」。每块屏幕都有自己的显示管道。
我画了一张架构图,帮你理清关系:
说白了,应用层只管「画」,系统服务层管「怎么摆、摆在哪」,硬件层管「最终输出」。这三层各司其职,缺一不可。
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:查看窗口信息,包括 DisplayIddumpsys 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 的窗口调度,再到具体的适配策略,每一步都需要仔细设计。
我个人觉得,最重要的三点:
- 理解架构:应用层、系统服务层、硬件层各司其职
- 动态适配:不要硬编码 DisplayId,通过 DisplayManager 动态管理
- 性能优先:减少图层数量,合理使用 HWC 合成
多屏系统就像一场交响乐,每个乐器(屏幕)都有自己的旋律,但需要指挥(SurfaceFlinger + WindowManager)统一协调。希望今天的分享能帮你更好地理解这场「交响乐」的运作方式。
公众号:蓝海资料掘金营,微信deep3321