10、车辆属性与信号:VHAL、属性读取、变化监听与模拟器数据
好,咱们进入正题。这一章聊的是 Android Automotive OS 里最核心的“神经末梢”——车辆属性与信号。说白了,就是你的车机怎么知道车现在跑多快、门关没关、空调设了多少度。
我个人习惯把 VHAL 理解成“车和系统之间的翻译官”。没有它,上层 App 就是瞎子。今天咱们就把这个翻译官的工作机制、怎么调用它、怎么监听变化,以及在没有真车的时候怎么在模拟器里模拟数据,一次性讲透。
10.1 Vehicle HAL 到底是什么?
VHAL,全称 Vehicle Hardware Abstraction Layer。名字很长,但核心就一句话:它把千奇百怪的汽车硬件信号,抽象成 Android 系统能理解的统一接口。
你想想看,不同车企的 CAN 总线协议、LIN 总线协议、以太网协议,千差万别。如果没有 VHAL,上层 App 每适配一款车就得重写一套代码,那还得了?
VHAL 的职责很清晰:
- 定义属性:比如车速、电量、车门状态,每个属性都有一个唯一的 ID。
- 提供读写接口:上层通过 VHAL 读属性值,或者下发控制指令(比如锁车)。
- 事件通知:属性变化时,VHAL 主动向上层推送。
核心要点:VHAL 是一个 HAL 层服务,运行在 Native 层。它通过 HIDL / AIDL 接口与 CarService(Java 层)通信。CarService 再通过 Car API 暴露给 App。
嗯,这里要注意:从 Android 11 开始,Google 推荐使用 AIDL 替代 HIDL。如果你还在用 HIDL 写新项目,我建议你尽快迁移。我在项目中遇到过因为 HIDL 接口版本不兼容导致的编译问题,折腾了两天才搞定。
10.2 车辆属性的定义与分类
车辆属性在代码里是用 VehicleProperty 枚举定义的。每个属性都有:
- 属性 ID:唯一标识,比如
VEHICLE_PROPERTY_PERF_VEHICLE_SPEED是 0x1560。 - 类型:INT32、FLOAT、STRING、BOOLEAN 等。
- 访问权限:只读、只写、读写。
- 变化模式:静态(不会变)、连续变化(比如车速)、按需变化(比如里程)。
我整理了一份常用属性表,你写代码时可以直接参考:
| 属性名称 | 属性 ID | 类型 | 说明 |
|---|---|---|---|
| PERF_VEHICLE_SPEED | 0x1560 | FLOAT | 车速,单位 km/h |
| PERF_ODOMETER | 0x1561 | FLOAT | 总里程,单位 km |
| DOOR_POS | 0x1A00 | INT32 | 车门位置状态 |
| HVAC_TEMPERATURE_SET | 0x2800 | FLOAT | 空调设定温度 |
| BATTERY_LEVEL | 0x3A00 | INT32 | 电池电量百分比 |
小技巧:属性 ID 的分配是有规律的。0x1000-0x1FFF 是车身相关,0x2000-0x2FFF 是 HVAC,0x3000-0x3FFF 是动力系统。记住这个规律,查文档时能快不少。
10.3 读取车辆属性
在 App 层读取车辆属性,核心就两步:拿到 Car 对象,然后通过 CarPropertyManager 读取。
看代码:
// 1. 获取 Car 实例
Car car = Car.createCar(context);
// 2. 获取 CarPropertyManager
CarPropertyManager propertyManager =
(CarPropertyManager) car.getCarManager(Car.PROPERTY_SERVICE);
// 3. 读取车速(同步方式)
float speed = propertyManager.getProperty(
Float.class,
VehiclePropertyIds.PERF_VEHICLE_SPEED,
0
);
// 4. 读取车门状态(需要指定区域 ID)
int doorState = propertyManager.getProperty(
Integer.class,
VehiclePropertyIds.DOOR_POS,
VehicleArea.DOOR_ROW_1_LEFT
);
这里有个坑:区域 ID。很多属性是分区域的,比如左前门和右后门的状态不一样。如果你不传区域 ID,默认是 0,可能读不到你想要的数据。我曾经因为忘了传区域 ID,调试了半天才发现读的一直是默认值。
另外,getProperty 是同步调用。如果 VHAL 响应慢,可能会阻塞主线程。我建议在子线程里调用,或者用异步方式:
propertyManager.getPropertyAsync(
Float.class,
VehiclePropertyIds.PERF_VEHICLE_SPEED,
0,
Runnable::run, // 回调执行的线程
(value, error) -> {
if (error == null) {
// 处理 value
}
}
);
10.4 监听属性变化
光能读还不够,很多时候你需要实时知道属性变了。比如车速变化时更新仪表盘显示。
监听的核心是注册 CarPropertyEventCallback:
CarPropertyEventCallback callback = new CarPropertyEventCallback() {
@Override
public void onChangeEvent(CarPropertyValue value) {
// 属性值变化时触发
float speed = value.getValue();
Log.d("Speed", "当前车速: " + speed);
}
@Override
public void onErrorEvent(int propertyId, int zone) {
// 读取出错时触发
Log.e("Speed", "读取车速失败,属性ID: " + propertyId);
}
};
// 注册监听
propertyManager.registerCallback(callback,
VehiclePropertyIds.PERF_VEHICLE_SPEED,
0 // 区域 ID,0 表示所有区域
);
// 记得在不需要时取消注册
// propertyManager.unregisterCallback(callback);
重要提醒:onChangeEvent 的回调频率取决于 VHAL 的实现。有些 VHAL 每秒上报 100 次,有些只在上次值变化超过阈值时才上报。如果你的 UI 更新太频繁,记得做防抖处理。
嗯,这里有个经验:我在做仪表盘项目时,车速回调每秒来了 60 次。如果每次回调都去刷新 UI,界面会卡成 PPT。后来我加了个 100ms 的节流阀,问题就解决了。
10.5 模拟器中的车辆数据
没有真车怎么调试?别担心,Android 模拟器提供了车辆数据模拟功能。
模拟器里跑的是 android.hardware.automotive.vehicle@2.0-default-service 这个默认实现。它内置了一套虚拟的 VHAL,会生成一些固定的车辆数据。
但固定数据不够用啊。比如你想测试车速从 0 加速到 120 的场景,怎么办?
两种方式:
方式一:通过命令行发送模拟数据
# 设置车速为 60 km/h
adb shell dumpsys car_service set-property \
--property 0x1560 \
--value 60.0 \
--area 0
# 读取当前车速
adb shell dumpsys car_service get-property 0x1560
这个命令我用的最多。写个脚本,循环改变车速值,就能模拟加速、减速、匀速等各种场景。
方式二:编写自定义 VHAL 实现
如果你需要更复杂的模拟逻辑,比如根据油门踏板位置计算车速,那就得自己写一个 VHAL 实现。这个比较重,一般用于集成测试。
简单说,你需要:
- 实现
IVehicle接口 - 在
onCreate里初始化属性值 - 在
get方法里返回当前值 - 在
set方法里处理写入请求 - 用定时器模拟属性变化,调用
onPropertyEvent通知上层
我的建议:大部分场景用命令行模拟就够了。只有当你需要测试 VHAL 的边界情况(比如同时监听 20 个属性、高频写入等)时,才考虑写自定义 VHAL。
10.6 本章知识体系
下面这张图总结了 VHAL 的核心工作流程,从 App 到硬件,一目了然:
从这张图你能看到:App 通过 CarService 调用 VHAL,VHAL 再跟硬件打交道。左侧是事件监听,右侧是属性读写,底部是模拟器调试方式。整个链路清晰明了。
10.7 避坑指南
最后,分享几个我踩过的坑:
- 属性 ID 别写死:不同车企可能自定义属性 ID。写死的话,换辆车就崩。建议从
VehiclePropertyIds常量引用。 - 区域 ID 别漏传:多区域属性(比如车门、座椅)必须传区域 ID,否则读到的可能是 0 或异常值。
- 回调别做耗时操作:
onChangeEvent是在 Binder 线程池里回调的。别在里面做网络请求或数据库写入,会阻塞其他回调。 - 模拟器数据别当真:模拟器返回的数据是固定的,不能代表真实车辆行为。真车测试前,务必用实车验证一遍。
我曾经在模拟器上跑得好好的,上了真车发现车速回调频率完全不一样。后来才意识到,模拟器的 VHAL 实现太“理想化”了。所以,模拟器只适合做功能验证,性能测试和边界测试一定要上真车。
好了,这一章的内容就到这里。VHAL 是车机开发的基石,理解透了,后面的章节会轻松很多。