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

好,我们直接进入正题。CarService 是整个 Android Automotive OS 的“心脏”。你想想看,车机上所有的硬件——空调、车窗、方向盘按键、车门锁——最终都要通过它来和上层 App 沟通。说白了,没有 CarService,你的车载系统就是个普通平板。

这一节,我会带你拆解三个核心问题:CarService 是怎么启动的?它暴露了哪些 API?以及它怎么跟底层的 Vehicle HAL 打交道? 这三个问题搞清楚了,你对整个车载系统的架构就有了骨架级的认知。

4.1 CarService 的启动流程

CarService 不是一个普通的 Android Service。它是在系统启动早期,由 SystemServer 主动拉起来的。我记得第一次看这个流程时,找了半天没找到 AndroidManifest.xml 里的声明——因为它根本就不是通过 PackageManager 启动的。

4.1.1 启动入口:SystemServer

SystemServer.javarun() 方法里,有一个专门的阶段叫 startCarService()。代码大致长这样:

// frameworks/base/services/java/com/android/server/SystemServer.java
private void startCarService() {
    if (!isAutomotive()) {
        return; // 非车机设备直接跳过
    }
    try {
        // 加载 CarService 的 jar 包
        Class<?> clazz = Class.forName("com.android.car.CarService");
        Service carService = (Service) clazz.newInstance();
        // 绑定到 SystemServer 的 ServiceManager
        ServiceManager.addService("car_service", carService);
    } catch (Exception e) {
        Slog.e(TAG, "Failed to start CarService", e);
    }
}

嗯,这里要注意:CarService 的 jar 包是预编译在 /system/framework/ 下的,不经过普通的 APK 安装流程。这样做的好处是——启动速度极快,不依赖 PackageManager 的扫描。

4.1.2 CarService 内部初始化

一旦 CarService 被实例化,它的 onCreate() 方法会做几件关键的事:

  1. 加载 Vehicle HAL 库:通过 JNI 调用 android.hardware.automotive.vehicle@2.0 或更高版本的 HAL。
  2. 启动 VehicleHal 线程:这是一个独立的 native 线程,负责轮询车辆硬件事件。
  3. 注册各个子服务:比如 CarPropertyServiceCarSensorServiceCarAudioService 等。
  4. 通知系统就绪:通过 SystemStateListener 告知 SystemServer:“我准备好了,上层可以开始调用了。”

核心要点:CarService 的启动是同步阻塞的。SystemServer 会等它初始化完成,才继续启动其他系统服务。所以如果 Vehicle HAL 挂了,整个车机都会卡在启动动画上。我在项目中遇到过供应商提供的 HAL 实现有内存泄漏,导致每次冷启动都要等 30 秒以上——排查起来非常痛苦。

4.2 CarService API 体系

CarService 暴露的 API 分为两大类:系统级 API(标记为 @SystemApi)和 公开 API(标记为 @CarApi)。前者只有系统应用能调用,后者第三方应用也能用。

4.2.1 核心 API 一览

API 名称 功能描述 访问级别
CarPropertyManager 读写车辆属性(车速、里程、车门状态等) 系统 + 第三方
CarSensorManager 获取传感器数据(加速度、陀螺仪、GPS 等) 系统 + 第三方
CarAudioManager 控制音频焦点、音量、音区 系统
CarHvacManager 空调控制(温度、风量、风向) 系统
CarDiagnosticManager 读取车辆诊断信息(OBD-II 相关) 系统

我个人习惯把 CarPropertyManager 称为“万能 API”。为什么?因为几乎所有车辆状态都可以通过它来读写。你想想看,车速、车门锁、车窗位置、甚至方向盘角度——底层都是通过 Property ID 来区分的。

4.2.2 使用示例:读取车速

// 获取 Car 实例
Car car = Car.createCar(context);
// 等待连接
car.connect();
// 获取 PropertyManager
CarPropertyManager propertyManager = 
    (CarPropertyManager) car.getCarManager(Car.PROPERTY_SERVICE);
// 读取车速(单位:km/h)
CarPropertyValue<Float> speedValue = 
    propertyManager.getProperty(Float.class, VehiclePropertyIds.PERF_VEHICLE_SPEED, 0);
float speed = speedValue.getValue();
Log.d(TAG, "当前车速: " + speed + " km/h");

避坑指南:我曾经在读取车速时忘了检查 speedValue 是否为 null。结果在车辆未启动(HAL 未就绪)时直接 crash 了。记住:所有 getProperty 调用都要判空,因为 HAL 可能返回 Status.NOT_AVAILABLE

4.3 Vehicle HAL 接口

Vehicle HAL 是 CarService 和硬件之间的“翻译官”。它定义在 hardware/interfaces/automotive/vehicle/ 目录下,使用 HIDL 或 AIDL 描述。

4.3.1 HAL 的核心数据结构

所有车辆数据都通过 VehiclePropValue 结构体传递:

// VehiclePropValue 的核心字段
struct VehiclePropValue {
    int32_t propId;          // 属性 ID,例如 VEHICLE_SPEED = 0x2910
    int32_t areaId;          // 区域 ID,例如左前门 = 0x01
    int32_t status;          // 状态:AVAILABLE, UNAVAILABLE, ERROR
    int64_t timestamp;       // 时间戳(纳秒)
    union {
        int32_t int32Values[];
        float floatValues[];
        int64_t int64Values[];
        bytes stringValue;
    } value;                 // 实际数据
};

为什么要有 areaId?因为一辆车可能有四个车门、两个空调区域。你想想看,设置左前车窗和右后车窗,用的 propId 相同,但 areaId 不同。这个设计非常巧妙——用一套 API 覆盖了所有多区域设备。

4.3.2 HAL 的两种工作模式

Vehicle HAL 支持两种数据传递方式:

  • 订阅模式(subscribe):CarService 注册一个回调,HAL 在数据变化时主动推送。适合车速、转速等高频变化的数据。
  • 轮询模式(get/set):CarService 主动调用 HAL 的 get()set() 方法。适合车门状态、空调设置等低频操作。

我个人建议:能用订阅就别用轮询。我曾经接手过一个项目,前任工程师用轮询方式每秒读 10 次车速,结果 CPU 占用率直接飙到 30%。改成订阅后,降到 2% 不到。

4.3.3 HAL 接口定义示例(AIDL 版本)

// IVehicle.aidl
interface IVehicle {
    // 获取属性值
    VehiclePropValue get(int32_t propId, in GetValueRequest request);
    // 设置属性值
    Status set(in SetValueRequest request);
    // 订阅属性变化
    Status subscribe(in ISubscriptionCallback callback, 
                     in SubscribeOptions options);
    // 取消订阅
    Status unsubscribe(in ISubscriptionCallback callback, 
                       int32_t propId);
}

重要提醒:HAL 接口的调用是跨进程的。每次 get()set() 都会触发一次 Binder 调用。如果你在 UI 线程频繁调用,ANR 是迟早的事。我建议所有 HAL 操作都放到后台线程,或者使用 CarPropertyManager 提供的异步回调版本。

4.4 整体架构图

下面这张图展示了 CarService、API 层和 Vehicle HAL 之间的关系。我特意把数据流向画了出来,方便你理解。

CarService 核心架构图 应用层 (App) 第三方 App / 系统 App CarService API 层 CarPropertyManager | CarSensorManager | CarAudioManager | CarHvacManager @SystemApi / @CarApi CarService 核心 服务注册 | 权限管理 | 订阅管理 | HAL 桥接 Vehicle HAL get() / set() / subscribe() / unsubscribe() 数据流向

从上图你可以看到,数据流是单向的:App 通过 CarService API 调用 CarService,CarService 再通过 Vehicle HAL 与硬件交互。反过来,硬件事件也是通过 HAL 回调 -> CarService -> API -> App 这条路径传递的。

4.5 小结

这一节我们聊了三个核心话题:

  • CarService 启动流程:由 SystemServer 在启动早期直接加载,不依赖 PackageManager。
  • CarService API:分为系统级和公开级,CarPropertyManager 是最常用的万能 API。
  • Vehicle HAL 接口:通过 VehiclePropValue 结构体传递数据,支持订阅和轮询两种模式。

嗯,说实话,这三个知识点是车载开发的“三座大山”。翻过它们,后面的路就好走了。如果你在实际开发中遇到 CarService 启动失败或者 HAL 调用超时的问题,欢迎随时交流——我在这些坑里摔过不少次,经验还算丰富。

一句话总结:CarService 是桥梁,API 是桥面,HAL 是桥墩。桥面坏了可以修,桥墩塌了——嗯,那就要找供应商了。

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