第1章 车载多屏交互:仪表盘集成、HUD显示、后排娱乐屏、多屏协同
各位同学,今天我们来聊聊车载多屏交互。
说实话,这个题目在Android Automotive开发里,算是比较有挑战的一块。我最早接触这个领域,是在一个量产项目上。当时客户要求仪表盘、中控、HUD三屏联动,我心想这不就是多几个SurfaceView嘛?结果一上手才发现,事情远没那么简单。
1.1 车载多屏的硬件架构
先看硬件层面。车载多屏系统,说白了就是多个显示设备挂在一个或多个SoC上。常见的架构有两种:
- 单SoC多显示输出:一个芯片驱动多个屏幕,比如高通SA8155,支持最多4路显示输出。
- 多SoC分布式架构:每个屏幕有自己的处理器,通过以太网或PCIe通信。
我个人习惯用第一种方案,因为开发简单,调试方便。但要注意,单SoC方案对内存带宽要求很高。我在项目中遇到过,仪表盘和中控同时渲染动画时,帧率直接掉到20fps。后来加了硬件合成器才解决。
核心要点:多屏交互的底层依赖显示控制器(Display Controller)和合成器(Composer)。Android Automotive使用HWC(Hardware Composer)来管理多个显示层。
1.2 仪表盘集成
仪表盘是车载系统里最敏感的部分。为什么?因为它直接关系到驾驶安全。
Android Automotive里,仪表盘通常作为一个独立的Activity运行,但它的显示不走常规的WindowManager。嗯,这里要注意:仪表盘需要实时渲染,延迟不能超过16ms。
// 仪表盘Activity的典型配置
<activity
android:name=".DashboardActivity"
android:persistableMode="persistAcrossReboots"
android:configChanges="orientation|screenSize"
android:excludeFromRecents="true"
android:taskAffinity="com.android.car.dashboard">
<meta-data
android:name="android.car.PRIMARY_DRIVER_DISPLAY"
android:value="true" />
</activity>
这段代码里,persistableMode和excludeFromRecents是关键。我曾经踩过一个坑:没加persistableMode,结果仪表盘在系统重启后无法自动恢复,用户得手动点开。这在量产车上绝对不行。
个人经验:仪表盘的UI尽量用Canvas或OpenGL直接绘制,别用复杂的布局嵌套。你想想看,一个RelativeLayout套三个LinearLayout,渲染时间直接翻倍。
1.3 HUD显示
HUD(抬头显示)是另一个有意思的东西。它把信息投射到前挡风玻璃上,要求内容简洁、亮度自适应。
Android Automotive里,HUD通常作为一个叠加层(Overlay)存在。它不占用主显示区域,而是通过硬件叠加层直接输出。
// HUD窗口的创建
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
PixelFormat.TRANSLUCENT);
// 设置HUD的显示区域
params.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
params.y = 50; // 距离顶部50像素
为什么用TYPE_APPLICATION_OVERLAY?因为HUD需要悬浮在其他应用之上,但又不能抢焦点。我记得有一次,HUD窗口不小心设置了FLAG_NOT_TOUCHABLE,结果触摸事件穿透到了仪表盘,导致用户误操作。嗯,这个坑我记了很久。
避坑指南:HUD的亮度调节一定要用系统亮度接口,别自己写PWM控制。我曾经试过直接操作背光驱动,结果在某个车型上导致屏幕闪烁,差点被客户投诉。
1.4 后排娱乐屏
后排娱乐屏相对简单,但有个特殊要求:独立音频输出。前排听导航,后排看电影,互不干扰。
Android Automotive里,后排娱乐屏通常运行一个独立的用户会话(User Session)。每个会话有自己的音频焦点和显示策略。
// 后排娱乐屏的音频策略配置
<audioPolicyConfiguration version="1.0">
<globalConfiguration speaker_drc_enabled="false">
<devicePorts>
<devicePort tagName="Rear Entertainment" type="AUDIO_DEVICE_OUT_BUS" role="source">
<profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
samplingRates="48000"
channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
</devicePort>
</devicePorts>
</globalConfiguration>
</audioPolicyConfiguration>
这段配置定义了一个独立的音频输出设备。我在项目里遇到过一个问题:后排娱乐屏播放视频时,前排的导航语音突然没了。后来发现是音频焦点没处理好。解决方案是给后排娱乐屏分配一个独立的音频流,不参与焦点竞争。
1.5 多屏协同
多屏协同是本章的重点。说白了,就是让多个屏幕像一个整体一样工作。
Android Automotive提供了CarMultiDisplayManager来管理多屏交互。核心功能包括:
- 跨屏拖拽:用户可以把中控的导航卡片拖到仪表盘上。
- 内容镜像:后排娱乐屏可以镜像中控的内容。
- 任务迁移:一个应用可以在不同屏幕间无缝切换。
// 跨屏拖拽的实现
CarMultiDisplayManager multiDisplayManager =
(CarMultiDisplayManager) car.getCarManager(Car.MULTI_DISPLAY_SERVICE);
// 注册跨屏拖拽监听
multiDisplayManager.registerDragListener(new CarMultiDisplayManager.DragListener() {
@Override
public void onDragStart(int sourceDisplayId, DragEvent event) {
// 拖拽开始,记录源屏幕
}
@Override
public void onDragEnd(int targetDisplayId, DragEvent event) {
// 拖拽结束,在目标屏幕显示内容
}
});
为什么会需要跨屏拖拽?你想想看,用户开车时想把导航信息从仪表盘移到HUD上,如果还要手动操作,那多危险。跨屏拖拽让这一切变得自然。
核心逻辑:多屏协同的本质是显示ID和输入事件的映射。每个屏幕有唯一的displayId,系统根据displayId分发触摸事件和渲染指令。
1.6 多屏交互的核心架构图
下面这张图展示了多屏交互的整体架构。我建议你仔细看看,理解每个组件之间的关系。
这张图里,从上到下依次是应用层、框架层、系统服务层和硬件层。多屏交互的核心在框架层和系统服务层。CarMultiDisplayManager负责协调多屏的显示策略,SurfaceFlinger负责实际的合成输出。
1.7 多屏协同的实战要点
最后,我总结几个实战中容易忽略的点:
- 显示ID的管理:每个屏幕的displayId是固定的,但不同车型可能不同。建议在代码里用常量定义,别硬编码。
- 触摸事件的隔离:后排娱乐屏的触摸事件不能影响到前排。用
InputFilter做事件分发过滤。 - 性能监控:多屏渲染对GPU压力很大。我建议用
dumpsys gfxinfo定期检查帧率。 - 电源管理:后排娱乐屏息屏后,要释放对应的显示资源。否则你会发现电池掉得飞快。
小技巧:调试多屏交互时,用adb shell dumpsys display可以查看所有屏幕的状态。包括分辨率、刷新率、是否激活等。这个命令我几乎每天都要用。
好了,这一章的内容就到这里。多屏交互是个大话题,后面我们还会深入每个子系统的实现细节。记住一句话:多屏不是多个单屏的简单叠加,而是一个有机的整体。理解了这个,你就能做出真正好用的车载多屏系统。
公众号:蓝海资料掘金营,微信deep3321