多屏显示与交互:多DisplayPort管理、屏幕镜像与扩展、触摸事件分发、多屏交互设计
各位同学,今天我们来聊聊车载系统里一个非常“显眼”的话题——多屏显示与交互。说实话,这几年我经手的项目,几乎没有一台车是只带一块屏幕的。仪表盘、中控、副驾娱乐屏、后排头枕屏……屏幕越来越多,挑战也越来越大。
我个人习惯把多屏系统比作一个“小剧场”。每个屏幕都是舞台,而系统就是后台的导演。导演要管好每个舞台的灯光(显示)、演员(触摸事件),还得让不同舞台之间能互动。嗯,今天我们就来拆解这个“导演”的工作。
多DisplayPort管理:硬件层面的“分线盒”
先看硬件。i.MX 8系列处理器,比如i.MX 8QM,内部集成了多个显示控制器。每个控制器可以独立驱动一个DisplayPort接口。说白了,就是芯片本身就有能力同时输出多路视频信号。
我在项目中遇到过一个问题:客户要求三块屏幕同时显示不同内容,但硬件设计上只引出了两个DP接口。怎么办?后来我们用了DP MST(Multi-Stream Transport)技术,一个物理接口通过菊花链方式带两块屏幕。但要注意,MST对带宽有要求,4K@60Hz的屏幕就别想着串了。
在Android框架里,这对应的是DisplayManager服务。它会枚举所有物理显示设备,并为每个设备创建一个Display对象。代码层面,你会在SurfaceFlinger的配置文件中看到类似这样的定义:
// device/fsl/imx8qm/display_config.xml
<display>
<display-id>0</display-id>
<type>primary</type>
<connector>DP-0</connector>
<width>1920</width>
<height>720</height>
</display>
<display>
<display-id>1</display-id>
<type>external</type>
<connector>DP-1</connector>
<width>1280</width>
<height>720</height>
</display>
你想想看,每个display-id对应一个物理屏幕。系统启动时,SurfaceFlinger会读取这个配置,然后为每个屏幕创建独立的合成图层。
屏幕镜像与扩展:两种模式,两种思路
多屏有了,接下来就是怎么用。两种基本模式:镜像(Mirror)和扩展(Extend)。
镜像模式,说白了就是“你看到什么,我也看到什么”。主屏的内容完全复制到副屏。这在演示场景里很常见,比如司机把导航信息投射到仪表盘上。
实现上,镜像其实是在SurfaceFlinger里把主屏的图层数据直接复制一份,送到副屏的显示控制器。但这里有个坑——分辨率不同怎么办?我曾经遇到主屏是1920x720,副屏是1280x720,直接复制会导致副屏显示不全。解决方案是加一个缩放器(Scaler),在复制时做分辨率适配。
扩展模式,则是每个屏幕显示独立的内容。中控显示导航,副驾看视频,互不干扰。这种模式下,每个屏幕都有自己的Display对象和WindowManager栈。
两种模式的切换,在Android里通过DisplayManager.createMirrorDisplay()和Presentation类来实现。代码大致是这样:
// 创建镜像显示
DisplayManager dm = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
Display mirrorDisplay = dm.createMirrorDisplay(displayId, 1280, 720);
// 扩展显示:使用Presentation
MediaRoutePresentation presentation = new MediaRoutePresentation(context, display);
presentation.show();
触摸事件分发:别让手指“迷路”
多屏系统里,触摸事件的分发是个大难题。你想想看,如果副驾在触摸副屏,但事件却跑到了主屏上,那体验得多糟糕。
Android的触摸事件分发机制,核心是InputDispatcher。它会根据触摸坐标,找到对应的窗口。但在多屏场景下,每个屏幕有自己的坐标系。比如主屏坐标范围是(0,0)到(1920,720),副屏是(0,0)到(1280,720)。
我遇到过一个问题:副屏的触摸坐标被错误地映射到了主屏的窗口上。排查后发现,是InputReader在读取触摸设备时,没有正确关联到对应的Display。
/dev/input/下都有一个独立的event节点。在.idc配置文件中,必须明确指定该触摸屏属于哪个Display。我曾经因为漏配这个,导致副屏触摸永远点不到正确位置。
正确的做法是在idc文件中添加:
# /system/usr/idc/touchscreen.idc
touch.deviceType = touchScreen
touch.displayId = 1 # 指定属于副屏
这样,InputDispatcher就能根据displayId,把触摸事件正确分发到对应屏幕的窗口树里。
多屏交互设计:从“各自为政”到“协同作战”
最后,我们来聊聊交互设计。多屏不是简单的“各玩各的”,而是要让它们协同工作。
举个例子:副驾在副屏上选了一个导航目的地,这个信息能不能自动同步到主屏的导航应用里?或者,主屏在播放音乐,副屏能不能显示歌词和控制播放?
这些场景,在Android里通常通过MediaSession和自定义的跨进程通信来实现。我建议使用ContentProvider或BroadcastReceiver来做数据同步,而不是直接操作对方的UI组件。为什么?因为直接操作UI容易引发线程安全问题。
另外,还要考虑“焦点管理”。多屏系统里,每个屏幕可以有自己的焦点窗口。但有些系统级事件,比如“语音唤醒”,需要把焦点切到主屏。这时候,WindowManager的focusDisplayId就派上用场了。
好了,关于多屏显示与交互,我们就聊到这里。这块内容在实际项目中坑不少,但只要你理解了硬件资源分配、触摸事件映射和交互设计原则,大部分问题都能迎刃而解。