13、车辆属性系统:VHAL 属性定义、属性订阅、属性变化通知机制
各位同学,今天我们来聊聊 Android Automotive 里最核心的模块之一——VHAL 属性系统。
说实话,我刚接触 Automotive 时,第一反应是:这不就是个硬件抽象层吗?跟 HAL 有啥区别?
后来踩了几个坑才明白,VHAL 远不止是「抽象」那么简单。它其实是整车数字世界的神经中枢。车上每一个传感器、每一个执行器,最终都要映射成 VHAL 里的一个属性。
13.1 属性定义:从物理信号到数字资产
先问大家一个问题:车速、车门状态、空调温度,这些物理量怎么变成代码里的变量?
VHAL 的做法是——把它们全部抽象成 VehicleProperty。
核心思想:每个车辆信号都是一个属性,属性有固定的 ID、类型、权限、区域。
来看一个典型的属性定义:
// 车速属性
VehicleProperty.SPEED
- propertyId: 0x11100100
- type: FLOAT
- area: VEHICLE_AREA_GLOBAL
- access: READ
// 车门状态
VehicleProperty.DOOR_POS
- propertyId: 0x11400200
- type: INT32_VEC
- area: VEHICLE_AREA_DOOR_DRIVER | VEHICLE_AREA_DOOR_PASSENGER
- access: READ | WRITE
我个人习惯把属性分成三类:
- 传感器属性:只读,比如车速、转速、油量。说白了就是车告诉你「我现在咋样」。
- 控制属性:可读写,比如车窗、空调、门锁。你告诉车「你给我干啥」。
- 配置属性:基本不变,比如 VIN 码、车型配置。车出厂就定死了。
我在项目中遇到过一个问题:有人把车速定义成了 INT32,结果车速超过 200 km/h 时直接溢出。嗯,后来我们统一用 FLOAT 了。你想想看,物理世界哪有整数?
13.2 属性订阅:谁关心什么,自己来登记
属性定义好了,谁来用?
Android Automotive 里,上层应用和服务通过 CarPropertyManager 来订阅属性变化。
订阅机制其实很简单,就三步:
- 应用注册一个回调
- CarService 把订阅请求发给 VHAL
- VHAL 记录下「谁订阅了哪个属性」
代码示例:
// 订阅车速变化
CarPropertyManager manager = car.getCarPropertyManager();
manager.registerCallback(
VehicleProperty.SPEED,
new CarPropertyEventCallback() {
@Override
public void onChangeEvent(CarPropertyValue value) {
float speed = (float) value.getValue();
// 更新 UI 显示
}
},
0, // 不设采样率,有变化就通知
CarPropertyManager.SENSOR_RATE_NORMAL
);
小提示:采样率别设太高。我曾经见过有人把车速采样率设成 100ms,结果 CPU 占用直接飙到 30%。车上资源有限,别浪费。
这里有个关键点:订阅是分区域的。比如你只关心左前车门,那就只订阅 VEHICLE_AREA_DOOR_DRIVER。别一股脑全订阅了,浪费带宽。
13.3 属性变化通知:VHAL 怎么把消息推上来
好,现在应用订阅了,VHAL 也知道了。那当车速真的变了,VHAL 怎么通知上层?
流程是这样的:
物理传感器 → VHAL 驱动 → VehicleHalClient → CarService → CarPropertyManager → 应用回调
VHAL 内部维护了一个 属性变化队列。每次传感器数据更新,VHAL 会:
- 检查这个属性有没有被订阅
- 如果有,把变化事件塞进队列
- 通过 Binder 回调通知 CarService
- CarService 再分发给对应的应用
来看 VHAL 端的伪代码:
void onSensorUpdate(int propertyId, VehicleArea area, float value) {
// 1. 检查是否有订阅者
if (hasSubscriber(propertyId, area)) {
// 2. 构造变化事件
VehiclePropValue propValue = new VehiclePropValue();
propValue.prop = propertyId;
propValue.areaId = area;
propValue.value = value;
propValue.timestamp = SystemClock.elapsedRealtime();
// 3. 放入通知队列
mHalClient.sendPropertyChangeEvent(propValue);
}
}
注意:VHAL 的通知是异步的。别在回调里做耗时操作,否则会阻塞整个通知通道。我曾经见过有人直接在回调里写数据库,结果导致其他属性通知延迟了 200ms。
13.4 核心流程图:属性订阅与通知全链路
下面这张图是我自己画的,把整个流程串起来了。你仔细看一遍,基本就全懂了。
你看,整个链路其实就两条线:
- 蓝色线(订阅请求):从上往下,应用告诉 VHAL「我要关注这个属性」。
- 红色线(变化通知):从下往上,VHAL 把变化推给所有订阅者。
13.5 避坑指南:我踩过的三个坑
最后分享几个实战经验,都是真金白银换来的:
坑一:属性 ID 冲突
我曾经在项目里同时用了两个供应商的 VHAL 实现,结果他们用了同一个属性 ID 表示不同的东西。车速变成了发动机转速,你敢信?
解决方案:所有自定义属性必须走 0x2000 以上的厂商范围,并且统一登记管理。
坑二:订阅了不存在的属性
应用层订阅了一个属性,但 VHAL 根本没实现。结果回调永远不触发,应用一直显示「加载中」。
解决方案:订阅前先调用 getAvailableProperties() 确认一下。
坑三:通知频率过高导致 ANR
有些传感器(比如方向盘转角)变化非常快,每秒几百次。如果每个变化都通知,应用根本处理不过来。
解决方案:VHAL 端做去重和降频,或者应用端设置合理的采样率。
13.6 小结
好了,这一章的内容就这些。总结一下:
- 属性定义是基础,ID、类型、区域、权限一个都不能错
- 订阅机制是桥梁,谁关心什么,自己来登记
- 通知机制是核心,异步、去重、降频是三个关键点
嗯,VHAL 属性系统说白了就是「定义好、登记好、通知好」。把这三件事做好,整个车辆数据通道就稳了。