多屏交互:仪表盘投屏、后排娱乐屏、HUD抬头显示
说实话,多屏交互这块,是我在车载项目里踩坑最多的领域之一。你想想看,一个车里现在动不动就三块屏、五块屏,仪表盘、中控、副驾、后排、HUD……每块屏都有自己的性格,还得让它们协同工作。嗯,今天我们就来聊聊这个。
为什么多屏这么难搞?
我刚开始做车载时,以为多屏就是多画几个SurfaceView。后来发现完全不是那么回事。核心难点有三个:
- 显示同步:仪表盘和HUD显示的车速,必须完全一致。差一个像素都不行。
- 交互冲突:后排屏在调音量,中控也在调音量,到底听谁的?
- 资源竞争:三块屏同时渲染动画,GPU扛得住吗?
说白了,多屏交互的本质,是一个系统里多个显示设备之间的状态同步与资源调度。
Android车载的多屏架构
Android从10开始,正式支持多屏显示。核心是 DisplayManager 和 Presentation。但车载场景下,我们一般不用Presentation,而是用更底层的 SurfaceFlinger 和 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());
场景二:后排娱乐屏
后排娱乐屏,这个坑更多。你想想看,后排乘客想看电影,前排司机想听导航,声音怎么分配?
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
);
场景三:HUD抬头显示
HUD这块,说实话Android原生支持很弱。大部分车厂都是自己写HAL层驱动,然后通过 SurfaceControl 直接往HUD硬件送数据。
HUD的核心要求是低延迟和高刷新率。因为HUD是反射到挡风玻璃上的,延迟稍微大一点,驾驶员就会感觉信息「飘」。
我一般这样设计HUD的数据流:
- 传感器数据(车速、转速)直接走
Vehicle HAL到HUD服务 - 导航信息从中控App通过
Binder回调到HUD服务 - 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 查看当前所有显示设备的状态。我经常用这个命令排查「某块屏没显示」的问题,十有八九是显示设备没正确注册。
好了,多屏交互的核心内容就这些。记住一句话:多屏不是炫技,而是为了更好的驾驶体验。每增加一块屏,都要问自己:这块屏真的有必要吗?它会不会分散驾驶员的注意力?