6、多屏显示与窗口管理:Android Automotive多屏架构

多屏显示,是车载系统里最绕不开的话题。你想想看,一辆车里少说两三块屏,多的五六块都有。仪表盘、中控、副驾屏、后排娱乐屏……每块屏各司其职,但又得协同工作。Android Automotive 从底层就支持多屏,但真正用好它,还是有不少门道的。

我个人习惯把多屏系统比作一个「剧院」:DisplayManager 是舞台总监,WindowManager 是灯光师,SystemUI 是场务。各司其职,缺一不可。下面我们一层层拆开来看。

6.1 多屏架构:DisplayManager 与 WindowManager

先说 DisplayManager。它是 Android 系统里管理物理显示器的核心服务。每块屏幕接入系统,DisplayManager 都会给它分配一个唯一的 Display ID。这个 ID 很重要,后面所有窗口操作都离不开它。

核心概念:DisplayManager 负责「发现」和「配置」屏幕,WindowManager 负责「往屏幕上放窗口」。两者配合,才能实现多屏显示。

WindowManager 在多屏环境下的工作方式,和单屏有很大区别。单屏时,我们只需要关心一个窗口栈。多屏时,每个 Display 都有自己的窗口栈。也就是说,中控屏上的应用切换,不会影响仪表盘的显示。

我在项目中遇到过一个问题:副驾屏上的视频应用全屏播放时,中控屏的导航窗口突然被挤到后面去了。排查了半天,发现是 WindowManager 的窗口策略没配好。说白了,就是没告诉系统「副驾屏的窗口栈和中控屏的窗口栈是独立的」。

解决方式很简单,在配置里声明每个 Display 的窗口栈策略。代码示例如下:

// 为每个 Display 创建独立的窗口栈
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();

// 获取 Display 的上下文
Context displayContext = context.createDisplayContext(display);

// 在指定 Display 上启动 Activity
Intent intent = new Intent(context, VideoPlayerActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
displayContext.startActivity(intent);

小提示:FLAG_ACTIVITY_MULTIPLE_TASK 这个 flag 很关键。不加它,系统可能会把新窗口塞到已有窗口栈里,导致显示错乱。

6.2 多屏策略与配置:DisplayInfo 和 DisplayDevice

每块屏幕都有自己的「身份证」—— DisplayInfo。它记录了屏幕的尺寸、密度、刷新率、旋转角度等信息。拿到这些信息,我们才能决定怎么布局。

举个例子,仪表盘通常是 1280x480 的细长屏,中控是 1920x1080 的横屏,副驾可能是 1920x720 的宽屏。三块屏的 DPI 也可能不一样。如果不做适配,同一个 UI 组件在三块屏上显示效果会差很多。

我建议在系统启动时,先枚举所有 Display,然后根据 DisplayInfo 做差异化配置:

DisplayManager dm = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
Display[] displays = dm.getDisplays();

for (Display display : displays) {
    DisplayInfo info = new DisplayInfo();
    display.getDisplayInfo(info);
    
    // 根据屏幕类型做不同处理
    if (info.displayId == Display.DEFAULT_DISPLAY) {
        // 主屏(中控)配置
        setupMainDisplay(info);
    } else if (isInstrumentCluster(display)) {
        // 仪表盘配置
        setupClusterDisplay(info);
    } else {
        // 副驾或其他屏幕
        setupSecondaryDisplay(info);
    }
}

DisplayDevice 是更底层的概念。它代表物理显示设备,比如一块 LCD 面板。在 Android Automotive 里,系统通过 Hardware Composer 和 DisplayDevice 交互。这块一般由硬件厂商实现,应用层开发者接触不多。但了解它的存在,有助于理解为什么有些屏幕配置改不了——因为 DisplayDevice 的某些属性是硬件锁死的。

注意:不要试图在运行时动态修改 DisplayDevice 的物理参数(如分辨率、刷新率)。我曾经试过,结果屏幕直接黑掉了。这些参数必须在系统启动前通过设备树或内核参数配置好。

6.3 SystemUI 在多屏环境下的适配

SystemUI 是 Android 系统的「门面」——状态栏、导航栏、通知面板都是它管的。单屏时,SystemUI 只在一个 Display 上显示。多屏时,问题就来了:每块屏都需要自己的状态栏吗?导航栏放哪块屏上?

嗯,这里要注意。Android Automotive 的默认做法是:只有主屏(中控)显示 SystemUI。仪表盘和副驾屏通常不显示系统栏,而是由车载厂商自定义 UI。

但有些场景需要特殊处理。比如副驾屏上运行了第三方应用,用户需要返回键。这时候,要么在副驾屏上显示一个半透明的导航栏,要么通过手势操作。

我在项目中做过一个方案:为副驾屏单独启动一个 SystemUI 实例,但只显示导航栏,不显示状态栏。实现方式是在 SystemUI 的启动配置里,指定它绑定到哪个 Display:

// SystemUI 启动时,指定 Display
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = dm.getDisplay(Display.DEFAULT_DISPLAY); // 或者指定其他 Display ID

// 创建绑定到特定 Display 的 Context
Context displayContext = context.createDisplayContext(display);

// 启动 SystemUI 服务
Intent intent = new Intent(displayContext, SystemUIService.class);
displayContext.startService(intent);

经验之谈:多屏 SystemUI 适配最头疼的是焦点管理。用户在中控屏上操作时,副驾屏的 SystemUI 不能抢焦点。我建议用 InputMonitor 来拦截不必要的触摸事件,只让当前活跃屏幕的 SystemUI 响应输入。

6.4 车载多屏场景实战

聊完理论,我们看看实际场景。一辆典型的智能汽车,至少有三块屏:仪表盘、中控、副驾屏。每块屏的职责不同,窗口管理策略也不同。

屏幕 典型分辨率 主要功能 窗口策略
仪表盘 1280x480 车速、导航、告警 全屏独占,不允许其他窗口覆盖
中控 1920x1080 导航、音乐、设置 多窗口自由切换,支持分屏
副驾屏 1920x720 视频、游戏、社交 独立窗口栈,不影响主屏

仪表盘的窗口管理最严格。因为它涉及行车安全,不能有任何应用窗口遮挡。我建议把仪表盘设为「安全显示区域」,只允许系统级应用(如仪表盘应用)在上面绘制。其他应用一律禁止。

中控屏是「主战场」。导航、音乐、电话、设置都在这里。窗口策略要支持分屏和画中画。比如导航应用占上半屏,音乐应用占下半屏。Android Automotive 的 Multi-Window 模式在这里很实用。

副驾屏相对自由。乘客可以看视频、玩游戏,甚至投屏。但要注意,副驾屏上的音频不能干扰驾驶员的导航提示。我建议用 AudioFocus 机制来管理音频焦点,副驾屏播放视频时,自动降低导航音量。

下面这张图展示了三块屏的窗口管理架构:

车载多屏窗口管理架构 仪表盘 Display ID: 1 窗口栈:独占 安全显示区域 中控屏 Display ID: 0 窗口栈:多窗口 支持分屏/画中画 副驾屏 Display ID: 2 窗口栈:独立 不影响主屏 系统服务层 DisplayManager → 管理物理显示器 WindowManager → 管理窗口栈和焦点 硬件层(Hardware Composer + DisplayDevice)

从架构图可以看出,三块屏通过 DisplayManager 统一管理,WindowManager 为每块屏分配独立的窗口栈。系统服务层负责协调,硬件层负责最终渲染。这种分层设计,让每块屏都能独立工作,互不干扰。

实战建议:开发多屏应用时,先用 adb 命令查看当前系统的 Display 列表:adb shell dumpsys display。这个命令会列出所有 Display 的 ID、分辨率、密度等信息。拿到这些数据,你才能针对性地做适配。

最后说一句,多屏开发最忌讳「想当然」。你以为副驾屏的窗口不会影响中控屏,但实际测试时发现焦点被抢了、音频串了、触摸事件被拦截了……这些问题都很常见。我的经验是:每块屏单独测试,然后做交叉测试。尤其是焦点管理和音频焦点,一定要反复验证。

好了,多屏显示与窗口管理就聊到这里。这部分内容在实际项目中占比很大,建议你动手写个 Demo,在三块屏上分别跑不同的应用,感受一下窗口栈的独立性。

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