29、车载多屏交互:CarMultiDisplayManager、屏幕投射、跨屏拖拽、应用流转

多屏交互,是车载系统里最“炫”但也最“坑”的功能之一。

你想想看,一辆车里可能有三块屏:仪表盘、中控、副驾娱乐屏。甚至后排还有两块。这些屏幕怎么协同工作?怎么让用户觉得“嗯,这车机真聪明”,而不是“这什么鬼,拖都拖不动”?

我最早接触多屏是在一个高端车型项目里。客户要求副驾看视频,中控导航,仪表显示车速。听起来简单,但真正做起来,你会发现——屏幕之间的“沟通”远比想象中复杂。

29.1 CarMultiDisplayManager:多屏管理的“大脑”

Android Automotive OS 里,多屏管理的核心就是 CarMultiDisplayManager。它负责注册、查询、分配每个屏幕的显示区域和逻辑。

说白了,它就像个“屏幕管家”。每个屏幕都有一个唯一的 DisplayId,系统通过这个 ID 来区分它们。

核心概念:

  • DisplayId:每个物理屏幕的唯一标识。仪表盘通常是 DisplayId=0,中控是 DisplayId=1,副驾是 DisplayId=2。
  • ZoneId:逻辑区域。一个物理屏幕可以切分成多个 Zone,比如中控屏左边是导航,右边是音乐。
  • WindowManager:负责把应用窗口分配到对应的 Display 上。

我习惯在系统启动时,先通过 CarMultiDisplayManager 获取所有可用屏幕的列表。这样可以避免后续操作时找不到目标屏幕。

// 获取 CarMultiDisplayManager 实例
CarMultiDisplayManager displayManager = 
    (CarMultiDisplayManager) car.getCarManager(Car.CAR_MULTI_DISPLAY_SERVICE);

// 获取所有屏幕信息
List<DisplayInfo> displays = displayManager.getDisplayInfos();
for (DisplayInfo info : displays) {
    Log.d("MultiDisplay", "Display ID: " + info.getDisplayId() 
          + ", Type: " + info.getType());
}

这里有个坑:不要假设屏幕的顺序。我曾经在一个项目里,默认 DisplayId=0 是仪表盘,结果某次 OTA 升级后,系统把中控屏排到了 0 号位。嗯,那场面……所有导航信息都跑到了仪表盘上。后来我改用 getDisplayType() 来判断屏幕角色,再也没出过问题。

29.2 屏幕投射:把内容“扔”到另一块屏上

屏幕投射,就是让一个应用的内容显示在另一块屏幕上。最常见的场景:副驾在看视频,突然想投到中控屏上。

实现方式有两种:

  1. 基于 DisplayManager 的投射:把整个 Activity 或 View 迁移到目标 Display。
  2. 基于 MediaProjection 的投射:捕获屏幕内容,实时传输到另一块屏。

我个人更推荐第一种,因为它更稳定,而且系统原生支持。

// 把当前 Activity 投射到副驾屏
DisplayManager displayManager = 
    (DisplayManager) getSystemService(Context.DISPLAY_SERVICE);
Display[] displays = displayManager.getDisplays();
Display targetDisplay = null;

for (Display display : displays) {
    if (display.getDisplayId() == 2) { // 副驾屏
        targetDisplay = display;
        break;
    }
}

if (targetDisplay != null) {
    // 创建 Presentation
    Presentation presentation = new Presentation(this, targetDisplay);
    presentation.setContentView(R.layout.layout_video);
    presentation.show();
}

提示:Presentation 的生命周期和宿主 Activity 是绑定的。如果 Activity 被销毁,Presentation 也会消失。记得在 onPause 里处理一下。

我在项目中遇到过一个问题:投射后,副驾屏的触摸事件无法传递到主屏的应用。后来发现是 InputManager 的焦点分配没处理好。解决方案是手动设置 View.setFocusable(true),并注册触摸监听。

29.3 跨屏拖拽:从一块屏拖到另一块屏

跨屏拖拽,听起来很酷,做起来很烦。

用户想从副驾屏拖一张图片到中控屏的导航里。这涉及到两个屏幕之间的数据传递和坐标映射。

核心流程:

  • 起点:在源屏幕启动 Drag and Drop,把数据封装到 ClipData 里。
  • 传输:通过 DragShadowBuilder 显示拖拽阴影。
  • 终点:目标屏幕的 View 接收 DragEvent,解析数据。

但这里有个关键问题:坐标转换。两块屏的分辨率、DPI 可能完全不同。你拖拽的阴影在源屏上位置正确,到了目标屏可能就偏了。

// 在源屏幕启动拖拽
View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view);
ClipData data = ClipData.newPlainText("label", "Hello from other screen");
view.startDragAndDrop(data, shadowBuilder, null, 0);

// 在目标屏幕接收拖拽
targetView.setOnDragListener((v, event) -> {
    switch (event.getAction()) {
        case DragEvent.ACTION_DROP:
            ClipData.Item item = event.getClipData().getItemAt(0);
            String text = item.getText().toString();
            // 处理接收到的数据
            break;
    }
    return true;
});

警告:跨屏拖拽时,DragEvent 的坐标是相对于源屏幕的。你需要手动转换成目标屏幕的坐标。我曾经因为忘了做这个转换,导致用户拖拽时,阴影在目标屏上乱跳。解决方案是获取两个屏幕的 DisplayMetrics,然后做比例换算。

29.4 应用流转:让应用“跟着用户走”

应用流转,是比屏幕投射更高级的玩法。用户从副驾屏切换到中控屏,应用的状态、数据、UI 都无缝迁移过去。

这背后依赖的是 CarMultiDisplayManagermoveTaskToDisplay() 方法。

// 把当前应用的任务迁移到目标屏幕
ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
int taskId = getTaskId();
am.moveTaskToDisplay(taskId, targetDisplayId);

但光迁移任务还不够。应用的状态怎么保存?

我建议使用 SavedStateHandle 或者 ViewModel 来保存关键数据。这样在屏幕切换时,应用可以恢复到之前的状态。

举个例子:副驾屏上正在播放视频,进度条在 30 分钟。流转到中控屏后,视频应该从 30 分钟继续播放,而不是从头开始。

// 保存播放进度
public class VideoViewModel extends ViewModel {
    private final SavedStateHandle savedStateHandle;
    
    public VideoViewModel(SavedStateHandle savedStateHandle) {
        this.savedStateHandle = savedStateHandle;
    }
    
    public void saveProgress(long progress) {
        savedStateHandle.set("progress", progress);
    }
    
    public long getProgress() {
        return savedStateHandle.get("progress");
    }
}

提示:应用流转时,onConfigurationChanged() 会被触发。记得在这里重新绑定 UI 和数据。我习惯在 ViewModel 里统一管理状态,Activity 只负责展示。

29.5 知识体系总览

下面这张图,是我梳理的多屏交互核心逻辑。你看一眼,基本就能明白整个流程了。

车载多屏交互核心逻辑 CarMultiDisplayManager 屏幕投射 跨屏拖拽 应用流转 Presentation / MediaProjection 将 Activity 或屏幕内容 投射到目标 Display DragShadow + ClipData 跨屏坐标映射 数据传递与接收 moveTaskToDisplay ViewModel 保存状态 onConfigurationChanged 恢复 注意事项 DisplayId 不可假设顺序 | 坐标需跨屏转换 | 状态保存要完整 | 触摸焦点需手动分配

这张图里,CarMultiDisplayManager 是总入口。它向下分出三个核心能力:屏幕投射、跨屏拖拽、应用流转。每个能力都有对应的实现方式和注意事项。

嗯,这里要注意:这三个能力不是互斥的。你可以同时使用。比如,先投射一个视频到副驾屏,然后拖拽一个文件到中控屏,最后把整个应用流转到后排屏。只要你的架构设计得够灵活,这些都能实现。

我个人觉得,多屏交互最难的不是技术实现,而是用户体验设计。用户不会关心你用了什么 API,他们只关心“拖过去顺不顺”、“切换快不快”。所以,在开发过程中,多和 UI/UX 同学沟通,多做真机测试。

我曾经在一个项目里,因为忽略了屏幕刷新率的差异,导致拖拽动画在副驾屏上卡顿。后来发现副驾屏是 30Hz,中控屏是 60Hz。解决方案是在拖拽时,根据目标屏幕的刷新率动态调整动画帧率。

好了,多屏交互的内容就这些。记住:屏幕是死的,逻辑是活的。只要把 CarMultiDisplayManager 吃透,剩下的就是发挥你的想象力了。


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