多屏交互:仪表盘投屏、后排娱乐屏、HUD抬头显示

说实话,多屏交互这块,是我在车载项目里踩坑最多的领域之一。你想想看,一个车里现在动不动就三块屏、五块屏,仪表盘、中控、副驾、后排、HUD……每块屏都有自己的性格,还得让它们协同工作。嗯,今天我们就来聊聊这个。

为什么多屏这么难搞?

我刚开始做车载时,以为多屏就是多画几个SurfaceView。后来发现完全不是那么回事。核心难点有三个:

  • 显示同步:仪表盘和HUD显示的车速,必须完全一致。差一个像素都不行。
  • 交互冲突:后排屏在调音量,中控也在调音量,到底听谁的?
  • 资源竞争:三块屏同时渲染动画,GPU扛得住吗?

说白了,多屏交互的本质,是一个系统里多个显示设备之间的状态同步与资源调度

Android车载的多屏架构

Android从10开始,正式支持多屏显示。核心是 DisplayManagerPresentation。但车载场景下,我们一般不用Presentation,而是用更底层的 SurfaceFlingerHWC(硬件合成器)。

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

应用层 仪表App | 中控App | 后排娱乐App | HUD服务 框架层 DisplayManager | WindowManager | CarService 系统服务层 SurfaceFlinger | HWC | AudioFlinger 硬件层 GPU | 显示控制器 | 多路LVDS/DSI输出 每块屏独立渲染 通过HWC合成输出

你看,从应用层到硬件层,每一层都要为多屏做适配。我个人习惯把这块分成三个场景来讲:仪表盘投屏、后排娱乐屏、HUD抬头显示。

场景一:仪表盘投屏

仪表盘投屏,说白了就是把中控导航信息投射到仪表盘上。为什么这么做?因为驾驶员看仪表盘比看中控更安全,视线偏移小。

我在项目中遇到过一个问题:导航箭头在仪表盘上显示时,总是比中控慢半拍。后来发现是跨进程通信延迟导致的。

解决方案是用 CarPropertyManager 配合共享内存:

// 中控端发送导航信息
CarPropertyManager carProperty = new CarPropertyManager(...);
CarPropertyValue<byte[]> navData = new CarPropertyValue<>(
    CarPropertyId.NAVIGATION_DATA, 
    navigationBytes
);
carProperty.setProperty(navData);

// 仪表盘端接收
CarPropertyValue<byte[]> received = carProperty.getProperty(
    CarPropertyId.NAVIGATION_DATA, 
    0
);
NavigationData data = NavigationData.parseFrom(received.getValue());
我的经验:用CarPropertyManager做投屏时,数据包不要超过1KB。超过的话,建议用共享内存+Socket的方式。我曾经试过传一张缩略图,结果卡得仪表盘动画都掉帧了。

场景二:后排娱乐屏

后排娱乐屏,这个坑更多。你想想看,后排乘客想看电影,前排司机想听导航,声音怎么分配?

Android从11开始支持 AudioAttributes 的多路输出。但实际落地时,还需要硬件支持多路音频通道。

我建议这样设计:

  • 后排屏独立音频通道:通过 AudioManager.setPreferredDeviceForStrategy() 指定输出设备
  • 内容隔离:后排屏使用独立 TaskStack,不与中控共享Activity栈
  • 权限控制:后排屏不能操作驾驶相关的设置,比如调整ESP、切换驾驶模式
// 后排屏音频通道配置
AudioManager audioManager = getSystemService(AudioManager.class);
AudioDeviceInfo bluetoothHeadphones = findBluetoothDevice(audioManager);

AudioAttributes attrs = new AudioAttributes.Builder()
    .setUsage(AudioAttributes.USAGE_MEDIA)
    .setContentType(AudioAttributes.CONTENT_TYPE_MOVIE)
    .build();

audioManager.setPreferredDeviceForStrategy(
    attrs,
    bluetoothHeadphones
);
注意:后排屏如果使用独立音频通道,一定要处理好蓝牙切换的场景。我曾经遇到一个bug:后排乘客连了蓝牙耳机,结果前排导航声音也跑到耳机里去了。原因是AudioAttributes的匹配策略太宽泛了。

场景三:HUD抬头显示

HUD这块,说实话Android原生支持很弱。大部分车厂都是自己写HAL层驱动,然后通过 SurfaceControl 直接往HUD硬件送数据。

HUD的核心要求是低延迟高刷新率。因为HUD是反射到挡风玻璃上的,延迟稍微大一点,驾驶员就会感觉信息「飘」。

我一般这样设计HUD的数据流:

  1. 传感器数据(车速、转速)直接走 Vehicle HAL 到HUD服务
  2. 导航信息从中控App通过 Binder 回调到HUD服务
  3. HUD服务合成画面,通过 SurfaceFlinger 直接输出到HUD显示层
// HUD服务端接收导航回调
public class HUDService extends Service {
    
    private final IHUDNavigationCallback callback = new IHUDNavigationCallback.Stub() {
        @Override
        public void onNavigationUpdate(NavigationInfo info) {
            // 直接更新HUD画面
            runOnUiThread(() -> {
                hudSurfaceControl.setNavigationInfo(info);
                hudSurfaceControl.invalidate();
            });
        }
    };
    
    @Override
    public void onCreate() {
        // 注册导航回调
        NavigationService service = getNavigationService();
        service.registerNavigationCallback(callback);
    }
}

核心要点:HUD的刷新率建议做到60fps以上。如果低于30fps,驾驶员会明显感觉到闪烁。我在一个项目里因为GPU负载过高,HUD掉到了20fps,测试反馈说「眼睛都快瞎了」。

多屏交互的避坑指南

做了这么多项目,我总结了几条血泪教训:

问题 表现 解决方案
显示不同步 仪表盘和中控车速不一致 使用统一数据源,不要各自从CAN总线读
焦点冲突 后排屏抢了中控的触摸焦点 每块屏独立InputChannel,互不干扰
内存泄漏 切屏几次后内存暴涨 注意释放SurfaceControl和GraphicBuffer
音频串扰 导航和音乐混在一起 使用AudioAttributes严格区分用途

嗯,多屏交互这块,说白了就是「分」与「合」的平衡。分的是显示、音频、焦点;合的是数据、状态、体验。做得好,用户觉得理所当然;做不好,用户会觉得这车「精神分裂」。

我个人建议,在项目初期就定义好多屏交互规范文档,明确每块屏的职责、数据流向、冲突处理策略。别等到联调时才发现问题,那时候改起来成本就高了。

一个小技巧:调试多屏时,可以用 adb shell dumpsys display 查看当前所有显示设备的状态。我经常用这个命令排查「某块屏没显示」的问题,十有八九是显示设备没正确注册。

好了,多屏交互的核心内容就这些。记住一句话:多屏不是炫技,而是为了更好的驾驶体验。每增加一块屏,都要问自己:这块屏真的有必要吗?它会不会分散驾驶员的注意力?

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