4、CarService 核心服务:启动流程、API 架构与 Vehicle HAL 接口

好,我们进入第四章。这一章可以说是车载系统的中枢神经——CarService。说实话,很多刚接触 Android Automotive 的朋友,最容易在这里栽跟头。CarService 到底什么时候启动?它跟普通的 SystemService 有什么区别?Vehicle HAL 又是怎么跟底层打交道的?

别急,我们一个一个来拆解。我个人习惯是,先看整体脉络,再抠细节。所以先上一张图,让你对 CarService 的全貌有个直观印象。

CarService 核心架构总览 车载应用层 (Car App) CarManager / Car API (android.car.*) CarService (SystemServer 进程) CarServiceHelperService → CarService VehicleHalManager / CarPropertyService / CarDrivingStateService ... Vehicle HAL (硬件抽象层) IVehicle@2.0 / IVehicle@2.1 / AIDL Vehicle HAL 车载硬件 (CAN Bus / LIN / 以太网 / GPIO)

4.1 CarService 启动流程:从 SystemServer 到 Vehicle HAL

CarService 不是凭空冒出来的。它是在 SystemServer 启动过程中,由 CarServiceHelperService 这个“马甲”负责拉起来的。嗯,这里要注意,CarServiceHelperService 本身是一个普通的 SystemService,但它干的事可不普通。

启动流程大致分三步:

  1. SystemServer 启动 CarServiceHelperService —— 在 startOtherServices() 方法中,系统会判断当前设备是否是 Automotive 设备(通过读取 ro.car 属性)。如果是,就启动 CarServiceHelperService。
  2. CarServiceHelperService 绑定 CarService —— 它通过 Intent 显式启动 com.android.car.CarService,这个 Service 运行在 system 用户进程,而不是 SystemServer 进程。为什么这么做?说白了,是为了隔离。CarService 如果挂了,不会拖垮整个 SystemServer。
  3. CarService 初始化各个子服务 —— CarService 的 onCreate() 里会依次初始化 VehicleHalManager、CarPropertyService、CarDrivingStateService 等十几个子服务。每个子服务负责一个具体的车辆功能域。

核心要点: CarService 是一个独立的 APK (packages/services/Car/),它通过 Binder 与 SystemServer 中的 CarServiceHelperService 通信。这种架构设计,我在项目中遇到过好几次——当 CarService 因为 HAL 层崩溃而重启时,SystemServer 依然稳如泰山,用户甚至感觉不到异常。

来看一段关键代码,这是 CarServiceHelperService 中绑定 CarService 的逻辑:

// frameworks/base/services/core/java/com/android/server/car/CarServiceHelperService.java
public void onStart() {
    if (!isAutomotive()) {
        return; // 非车载设备直接跳过
    }
    Intent intent = new Intent();
    intent.setComponent(new ComponentName("com.android.car", "com.android.car.CarService"));
    if (!getContext().bindServiceAsUser(intent, mCarServiceConnection, Context.BIND_AUTO_CREATE,
            UserHandle.SYSTEM)) {
        Slog.wtf(TAG, "Cannot bind to CarService");
    }
}

看到没?bindServiceAsUser 绑定到 SYSTEM 用户。这意味着 CarService 与系统用户进程共存,拥有系统级权限,但又独立于 SystemServer 进程。我曾经踩过一个坑:在 CarService 里直接调用了 SystemServer 内部的方法,结果因为进程隔离,Binder 调用失败。后来才意识到,CarService 和 SystemServer 之间只能通过公开的 Binder 接口通信。

4.2 Car API 架构:应用层如何与车辆对话

应用开发者接触最多的,就是 android.car.* 包下的 Car API。这套 API 的设计思路,说白了就是“门面模式”——把复杂的车辆功能抽象成一个个 Manager,每个 Manager 对应一个车辆服务。

举个例子:

  • CarPropertyManager —— 读写车辆属性(车速、里程、油量等)
  • CarDrivingStateManager —— 获取驾驶状态(停车、行驶、辅助驾驶)
  • CarHvacManager —— 控制空调(温度、风量、模式)
  • CarAudioManager —— 管理车载音频焦点和路由

你想想看,如果让应用直接跟 Vehicle HAL 打交道,那画面太美不敢看——每个车厂的 HAL 实现都不一样,应用得适配多少种协议?所以 CarService 在中间做了一层封装,把 HAL 的原始数据转换成统一的 API 接口。

个人经验: 我建议你在写车载应用时,尽量使用 Car API,不要绕过 CarService 直接访问 HAL。原因很简单——CarService 做了权限校验、多用户隔离、状态缓存。直接访问 HAL 的话,这些保护机制就全绕过去了。我在一个项目中见过有人直接通过 JNI 调用 HAL,结果多用户切换时数据全乱套了。

Car API 的调用链路是这样的:

// 应用层
Car car = Car.createCar(context);
CarPropertyManager propertyManager = (CarPropertyManager) car.getCarManager(Car.PROPERTY_SERVICE);
// 获取当前车速
float speed = propertyManager.getProperty(Float.class, VehiclePropertyIds.PERF_VEHICLE_SPEED, 0);

这个调用会经过 Binder 到达 CarService 中的 CarPropertyService,然后 CarPropertyService 通过 VehicleHalManager 下发到 Vehicle HAL。整个过程是同步的,但 HAL 层也支持异步回调——比如车速变化时,HAL 会主动上报。

4.3 Vehicle HAL 接口定义:底层通信的契约

Vehicle HAL 是 CarService 与硬件之间的桥梁。它的接口定义在 hardware/interfaces/automotive/vehicle/ 目录下。目前主流的有两种版本:

版本 接口文件 特点
HIDL (2.0 / 2.1) IVehicle.hal 传统方式,稳定但扩展性一般
AIDL (Vehicle HAL) IVehicle.aidl 新趋势,支持更灵活的数据类型和回调

我个人更推荐新项目直接上 AIDL 版本的 Vehicle HAL。为什么?因为 HIDL 的 VehiclePropValue 是个大杂烩结构体,什么类型都往里塞,调试起来很痛苦。而 AIDL 版本支持泛型、可空类型、更清晰的接口定义。

来看一个典型的 Vehicle HAL 接口定义(AIDL 版本):

// hardware/interfaces/automotive/vehicle/aidl/android/hardware/automotive/vehicle/IVehicle.aidl
interface IVehicle {
    /**
     * 获取所有支持的属性列表
     */
    VehiclePropConfig[] getAllPropConfigs();

    /**
     * 获取指定属性的当前值
     */
    VehiclePropValue get(int propId, in GetRequest request);

    /**
     * 设置属性值(如控制空调、车窗等)
     */
    StatusCode set(in SetValueRequest request);

    /**
     * 订阅属性变化
     */
    StatusCode subscribe(in SubscribeRequest request);

    /**
     * 取消订阅
     */
    StatusCode unsubscribe(int propId, int zoneId);
}

这里要注意几个关键点:

  • propId —— 每个车辆属性都有一个唯一的 ID,定义在 VehiclePropertyIds 中。比如车速是 0x11100700,空调温度是 0x11200700。这些 ID 是车厂和 Google 共同维护的。
  • zoneId —— 表示车辆的区域。比如主驾座椅、副驾座椅、后排左侧空调。同一个属性在不同区域可能有不同的值。
  • VehiclePropValue —— 属性的值对象,包含时间戳、状态码、实际数据。数据可以是 int、float、String、byte[] 等。

避坑指南: 我曾经在项目中遇到过一个诡异的问题——设置空调温度时,明明调用了 set() 方法,返回值也是 OK,但空调就是不动作。后来排查发现,是 HAL 层对 zoneId 的解析有 bug。主驾的 zoneId 是 0x01,但 HAL 实现里写死了 0x00。所以,一定要确认车厂提供的 HAL 实现中 zoneId 的映射关系,否则你调破头也没用。

另外,Vehicle HAL 支持两种通信模式:

  1. 同步请求/响应 —— 比如 get() 和 set(),CarService 发起请求,HAL 返回结果。适用于查询当前状态、执行控制操作。
  2. 异步订阅/通知 —— 比如 subscribe() 之后,HAL 会在属性变化时主动回调。适用于车速、转速、故障码等需要实时监控的数据。

这两种模式在 CarService 内部是通过 VehicleHalManager 统一管理的。它维护了一个订阅者列表,当 HAL 上报事件时,分发给对应的 CarService 子服务,再通过 Binder 通知到应用层。

嗯,说到这里,我想起一个细节:在调试 HAL 层时,可以用 dumpsys car_service 命令查看当前 CarService 的状态,包括已订阅的属性列表、最近的属性变化值。这个命令在开发阶段非常有用,我几乎每天都要用几次。

最后,总结一下本章的核心脉络:

  • CarService 启动:SystemServer → CarServiceHelperService → CarService(独立进程)
  • Car API 架构:应用层通过 CarManager 访问 CarService,CarService 内部按功能域拆分子服务
  • Vehicle HAL 接口:定义车辆属性的读写和订阅契约,支持 HIDL 和 AIDL 两种形式

下一章我们会深入 Vehicle HAL 的具体实现,看看车厂是如何把 CAN 总线数据转换成 VehiclePropValue 的。不过那是后话了,先把今天的内容消化掉。


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