4、Car API 入门:认识 Car API 的核心类

说实话,刚接触 Android Automotive 的时候,我也有点懵。车载系统和手机开发完全是两码事。手机上的传感器、定位、网络,到了车上全变了味。你要面对的是车速、油量、车门状态、空调温度……这些数据怎么拿?靠的就是 Car API。

这一章,咱们就把 Car API 的三个核心类搞清楚:Car、CarManager、CarPropertyManager。说白了,它们就是你和车辆硬件之间的翻译官。

4.1 整体架构:一张图看懂

先别急着写代码。我习惯在动手之前,先把整个体系在脑子里过一遍。下面这张图,就是 Car API 的调用链路:

Car API 核心调用链路 你的 App(应用层) Car 对象(入口) CarManager 家族 CarPropertyManager | CarSensorManager | CarInfoManager | ... 车辆硬件(CAN Bus / HAL) 你的代码从这里开始 调用 getCarManager() 按需选择 不同 Manager

嗯,流程很清楚:你的 App 先拿到 Car 对象,然后通过它获取各种 CarManager,最后用 Manager 去读车辆数据。咱们一个一个来拆。

4.2 Car 类:一切的入口

Car 是什么?它就是你和车载系统之间的连接器。没有它,你什么都干不了。

核心要点:Car 对象是一个单例,代表当前车辆。你需要通过 Car.createCar() 来获取它,并且在使用前要调用 connect()。

我记得第一次写 Automotive 应用时,忘了调用 connect(),结果所有 Manager 都返回 null。排查了半天,最后发现是连接没建立。这个坑,我替你们踩过了。

来看一段标准用法:

// 1. 创建 Car 实例
Car car = Car.createCar(context);

// 2. 注册连接回调
car.setCarServiceCallback(new Car.CarServiceCallback() {
    @Override
    public void onConnected() {
        // 连接成功,可以获取 Manager 了
        Log.d("CarAPI", "车辆服务已连接");
    }

    @Override
    public void onDisconnected() {
        // 连接断开,需要重新连接
        Log.w("CarAPI", "车辆服务断开");
    }
});

// 3. 发起连接
car.connect();

注意:connect() 是异步操作。不要在调用 connect() 之后立即去拿 Manager,一定要等 onConnected() 回调触发。我曾经见过有人在这上面栽跟头,一上来就 NullPointerException。

4.3 CarManager 家族:各司其职

Car 对象连接成功后,你就可以通过它获取各种 Manager 了。每个 Manager 负责一类车辆功能。我整理了一个表格,方便你对照:

Manager 名称 负责功能 典型数据
CarPropertyManager 车辆属性(最常用) 车速、油量、里程、车门状态
CarSensorManager 传感器数据 环境温度、光照、压力
CarInfoManager 车辆基本信息 VIN码、品牌、型号、年份
CarHvacManager 空调控制 温度设定、风量、模式
CarAudioManager 车载音频 音量、音源切换、声道

你想想看,这么多 Manager,最常用的是哪个?没错,CarPropertyManager。车速、油量这些核心数据,全得靠它。

4.4 CarPropertyManager:读取车辆数据

这个类,说白了就是车辆属性的万能钥匙。它通过 Property ID 来区分不同的数据。每个 Property ID 对应一个车辆属性,比如:

  • VehiclePropertyIds.PERF_VEHICLE_SPEED —— 车速
  • VehiclePropertyIds.FUEL_LEVEL —— 油量
  • VehiclePropertyIds.ODOMETER —— 里程
  • VehiclePropertyIds.DOOR_POS —— 车门状态

获取这些数据的方式有两种:主动读取 和 监听变化。我建议你根据场景来选择。

4.4.1 主动读取:一次性获取

适合你只需要当前值的情况,比如用户点了一下「查看油量」按钮。

// 获取 CarPropertyManager
CarPropertyManager propertyManager = 
    (CarPropertyManager) car.getCarManager(Car.PROPERTY_SERVICE);

// 读取车速(单位:米/秒)
CarPropertyValue<Float> speedValue = 
    propertyManager.getProperty(
        VehiclePropertyIds.PERF_VEHICLE_SPEED, 
        0  // 区域 ID,0 表示整车
    );

if (speedValue != null) {
    float speedMps = speedValue.getValue();
    float speedKmph = speedMps * 3.6f;  // 转成公里/小时
    Log.d("CarAPI", "当前车速:" + speedKmph + " km/h");
}

小提示:车速的单位是米/秒,不是公里/小时。我刚开始做的时候直接用了原始值,结果显示 10 m/s,产品经理说「这车怎么才跑 10 码?」—— 其实已经 36 km/h 了。记得乘以 3.6。

4.4.2 监听变化:实时更新

适合需要持续跟踪的场景,比如导航界面实时显示车速。

// 注册监听器
propertyManager.registerCallback(callback, 
    VehiclePropertyIds.PERF_VEHICLE_SPEED,
    CarPropertyManager.CALLBACK_RATE_FAST  // 高频更新
);

// 回调实现
CarPropertyEventCallback callback = new CarPropertyEventCallback() {
    @Override
    public void onChangeEvent(CarPropertyValue value) {
        float speed = (float) value.getValue();
        updateSpeedUI(speed * 3.6f);  // 更新界面
    }

    @Override
    public void onErrorEvent(int propertyId, int zone) {
        Log.e("CarAPI", "读取属性失败:" + propertyId);
    }
};

// 记得在不用的时候取消注册
propertyManager.unregisterCallback(callback);

这里有个细节:CALLBACK_RATE_FAST 是最高频率,大概 10-20 Hz。如果你只是显示油量,用 CALLBACK_RATE_NORMAL 就够了,省电。

4.5 实战:读取车速和油量

光说不练假把式。咱们写一个完整的例子,把车速和油量都读出来。

public class VehicleDataReader {
    private Car car;
    private CarPropertyManager propertyManager;
    private Context context;

    public VehicleDataReader(Context context) {
        this.context = context;
    }

    public void connect() {
        car = Car.createCar(context);
        car.setCarServiceCallback(new Car.CarServiceCallback() {
            @Override
            public void onConnected() {
                propertyManager = (CarPropertyManager) 
                    car.getCarManager(Car.PROPERTY_SERVICE);
                Log.d("CarAPI", "连接成功,可以读取数据了");
                readAllData();
            }

            @Override
            public void onDisconnected() {
                Log.w("CarAPI", "连接断开");
            }
        });
        car.connect();
    }

    private void readAllData() {
        // 读取车速
        CarPropertyValue<Float> speed = propertyManager.getProperty(
            VehiclePropertyIds.PERF_VEHICLE_SPEED, 0);
        if (speed != null) {
            float kmh = speed.getValue() * 3.6f;
            Log.d("CarAPI", "车速:" + String.format("%.1f", kmh) + " km/h");
        }

        // 读取油量(0.0 ~ 1.0 之间的浮点数)
        CarPropertyValue<Float> fuel = propertyManager.getProperty(
            VehiclePropertyIds.FUEL_LEVEL, 0);
        if (fuel != null) {
            float percent = fuel.getValue() * 100f;
            Log.d("CarAPI", "油量:" + String.format("%.0f", percent) + "%");
        }
    }

    public void disconnect() {
        if (car != null) {
            car.disconnect();
        }
    }
}

关键点总结:

  • 油量的取值范围是 0.0 ~ 1.0,需要自己转成百分比
  • 车速单位是 m/s,记得转 km/h
  • 所有读取操作必须在 onConnected() 之后进行

4.6 避坑指南

做车载开发,坑是真的多。我把自己踩过的几个列出来,你们能少走弯路:

  • 我曾经在模拟器上测试,结果车速永远返回 0。后来才发现,模拟器默认不模拟车辆数据,需要手动在设置里开启「模拟车辆数据」选项。
  • 我曾经在油量读取时直接用了 int 类型,结果发现油量是 float,精度丢失,显示总是 0%。
  • 注意:不同车型支持的 Property ID 可能不一样。比如有的车不支持 FUEL_LEVEL(电动车),你得用 EV_BATTERY_LEVEL。写代码时一定要做 null 判断。

4.7 本章小结

这一章的内容,说白了就是三句话:

  1. Car 是入口,先连接再使用
  2. CarManager 是工具,按需选择
  3. CarPropertyManager 是主力,读车速油量就靠它