4. 车辆属性与 Vehicle HAL
说实话,刚接触 Android Automotive 的时候,我也有点懵。车载系统和手机开发完全是两码事。手机上的传感器、定位、网络,到了车上全变了味。你要面对的是车速、油量、车门状态、空调温度……这些数据怎么拿?靠的就是 Car API。
这一章,咱们就把 Car API 的三个核心类搞清楚:Car、CarManager、CarPropertyManager。说白了,它们就是你和车辆硬件之间的翻译官。
4.1 整体架构:一张图看懂
先别急着写代码。我习惯在动手之前,先把整个体系在脑子里过一遍。下面这张图,就是 Car API 的调用链路:
嗯,流程很清楚:你的 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 本章小结
这一章的内容,说白了就是三句话:
- Car 是入口,先连接再使用
- CarManager 是工具,按需选择
- CarPropertyManager 是主力,读车速油量就靠它