26、车载Android系统:CarService、车载HAL、多屏交互

车载Android,说白了就是把手机上的Android搬到了车上。但你别以为只是换个屏幕那么简单。车上的环境比手机复杂得多——空调、车窗、方向盘按键、仪表盘、中控屏、副驾屏……这些东西怎么协同工作?靠的就是今天要讲的这套体系。

我个人习惯把车载Android系统拆成三层来看:应用层(CarService)、硬件抽象层(车载HAL)、显示层(多屏交互)。这三层搞明白了,车载开发的基本盘就稳了。

核心观点:车载Android不是魔改Android,而是在标准Android框架上,叠加了一套汽车专属的服务和硬件抽象层。CarService是大脑,车载HAL是手脚,多屏交互是眼睛和耳朵。

一、CarService:车载应用的总管家

CarService是什么?它是Android Automotive OS里最核心的系统服务。你可以把它理解成一个专门管车的“系统级App”。它跑在system_server进程里,负责跟车相关的所有功能。

我记得第一次接触CarService时,翻代码翻得头大。后来发现,其实它主要干三件事:

  • 车辆属性管理——车速、车门状态、空调温度、油量……这些数据都通过CarService统一暴露给上层App。
  • 车辆功能控制——开空调、调音量、切换驾驶模式……App调用CarService的API,CarService再往下走。
  • 多用户与安全策略——驾驶员在开车时,副驾能不能看视频?CarService里有一套策略来控制。

来看一个最简单的例子:获取当前车速。

// 获取CarService实例
Car car = Car.createCar(context);
if (car == null) {
    // 车载环境才可用,模拟器里跑不了
    return;
}

// 获取车辆属性管理器
CarPropertyManager propertyManager = (CarPropertyManager) car.getCarManager(Car.PROPERTY_SERVICE);

// 读取车速(单位:km/h)
float speed = propertyManager.getFloatProperty(
    VehiclePropertyIds.PERF_VEHICLE_SPEED, 0);
Log.d("CarDemo", "当前车速:" + speed + " km/h");

你想想看,如果没有CarService,每个App都得自己去跟硬件打交道,那不乱套了?CarService就是中间人,把底层硬件抽象成统一的API。

避坑指南:我曾经在项目里直接在主线程调CarPropertyManager的get方法,结果UI卡成PPT。记住,CarService的某些属性读取是跨进程调用,耗时不确定,一定要放到子线程或者用回调方式。

二、车载HAL:连接硬件与系统的桥梁

CarService再牛,它也不能直接拧螺丝。真正跟硬件打交道的是车载HAL(Hardware Abstraction Layer)。

车载HAL和手机上的HAL不太一样。手机HAL管的是摄像头、传感器、音频这些。车载HAL管的是:

  • Vehicle HAL —— 车辆信号(车速、转向、灯光等)
  • Audio HAL —— 车载音响、麦克风阵列
  • Display HAL —— 多屏显示(中控、仪表、HUD)
  • Input HAL —— 方向盘按键、旋钮、触摸板

其中最重要的就是Vehicle HAL。它定义了一套标准的车辆属性ID(VehiclePropertyIds),CarService通过这套ID来读写车辆数据。

来看一个Vehicle HAL的典型实现片段:

// Vehicle HAL 中的属性订阅示例(C++)
Return<StatusCode> VehicleHal::subscribe(
    int32_t propertyId, 
    int32_t areaId, 
    float sampleRate) {
    
    // 检查属性是否支持
    if (!isPropertySupported(propertyId)) {
        return StatusCode::INVALID_ARG;
    }
    
    // 注册回调,当属性值变化时通知CarService
    mPropertyMap[propertyId].subscribers.push_back({
        .areaId = areaId,
        .sampleRate = sampleRate,
        .callback = [this](VehiclePropValue& value) {
            // 通过hidl/aidl回调到CarService
            mCallback->onPropertyEvent({value});
        }
    });
    
    return StatusCode::OK;
}

嗯,这里要注意:车载HAL是C++实现的,跑在独立的进程里。CarService通过AIDL/HIDL跟它通信。所以每次读写车辆属性,都是一次跨进程调用。性能敏感的场景(比如车速显示),一定要考虑延迟。

警告:千万不要在车载HAL里做耗时操作!我曾经见过一个供应商在HAL里加了网络请求来获取云端数据,结果导致车辆启动时CarService卡死30秒。HAL只做硬件读写,其他逻辑交给上层。

三、多屏交互:中控、仪表、副驾屏的协同

车载Android最头疼的问题之一就是多屏。手机只有一个屏,车上有三四个屏。而且每个屏的角色不一样:

屏幕 典型位置 主要用途 交互方式
中控屏 中央 导航、音乐、设置 触摸、语音
仪表屏 方向盘后 车速、续航、警示 方向盘按键
副驾屏 副驾前方 影音、游戏 触摸
HUD 前挡风 导航箭头、车速 无交互

多屏交互的核心问题有三个:

  1. 窗口管理——哪个App显示在哪个屏上?
  2. 焦点管理——方向盘按键控制哪个屏?
  3. 跨屏拖拽——导航能不能从中控拖到仪表?

Android原生的窗口管理(WindowManager)只支持单屏。车载上用的是CarWindowManager,它扩展了原生的窗口管理,支持多Display。

来看一个多屏显示的配置示例:

// 在CarService中注册多屏Display
CarDisplayManager displayManager = 
    (CarDisplayManager) car.getCarManager(Car.DISPLAY_SERVICE);

// 获取仪表屏的Display ID
int clusterDisplayId = displayManager.getDisplayIdForCluster();

// 将导航App显示到仪表屏
Intent intent = new Intent(context, NavigationActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 
                Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchDisplayId(clusterDisplayId);
context.startActivity(intent, options.toBundle());

我个人习惯把多屏交互的架构画成下面这张图,方便理解:

车载Android系统三层架构 应用层(CarService) 车辆属性管理 | 功能控制 | 多用户策略 | 安全约束 API: CarPropertyManager, CarAudioManager, CarParkingManager ... 服务层(System Services) CarServiceManager | VehicleHalManager | DisplayManager AIDL / HIDL 跨进程通信 硬件抽象层(车载HAL) Vehicle HAL | Audio HAL | Display HAL | Input HAL C++ 实现,直接操作硬件寄存器 / CAN总线 跨进程边界 多屏交互 中控 | 仪表 | 副驾 | HUD

从这张图可以看得很清楚:CarService在上层管业务逻辑,车载HAL在下层管硬件,中间通过系统服务层做桥接。多屏交互则贯穿整个架构,每个层级都要考虑。

四、实战中的几个坑

讲完理论,说几个我在项目中踩过的坑,希望对你有帮助。

坑1:CarService的启动顺序

我曾经在项目里遇到一个问题:车辆启动后,中控屏黑屏30秒才亮。查了半天,发现是CarService在等Vehicle HAL初始化。而Vehicle HAL又在等CAN总线唤醒。解决方案是把CarService的启动改成懒加载——先显示开机动画,等底层就绪了再加载车辆功能。

坑2:多屏的触摸事件冲突

有一次测试发现,副驾屏滑动时,中控屏的导航也跟着动。原因是两个屏的触摸事件被同一个InputDispatcher处理了。解决方案是在Display HAL里给每个屏分配独立的InputChannel,并在CarService里做焦点隔离。

坑3:车辆属性订阅的频控

车速显示要求每秒刷新30次,但Vehicle HAL的CAN总线读取上限是10Hz。如果CarService订阅频率超过硬件能力,会导致数据堆积和延迟。我当时的做法是在CarService里加一个频控层,把高频请求合并成低频轮询。

个人建议:刚开始做车载开发时,别急着写代码。先花一周时间把CarService的源码框架跑一遍,理解它的启动流程、属性订阅机制、多屏窗口管理。这些基础打牢了,后面遇到问题才能快速定位。

车载Android开发,说白了就是跟硬件打交道。CarService、车载HAL、多屏交互这三块,是绕不开的核心。你想想看,车上的每一个功能——从按一下空调按钮到仪表盘显示导航箭头——背后都是这三层在协同工作。搞懂了它们,你就掌握了车载Android的命脉。


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