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 来订阅属性变化。

订阅机制其实很简单,就三步:

  1. 应用注册一个回调
  2. CarService 把订阅请求发给 VHAL
  3. 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 会:

  1. 检查这个属性有没有被订阅
  2. 如果有,把变化事件塞进队列
  3. 通过 Binder 回调通知 CarService
  4. 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 属性订阅与通知全链路 App (CarPropertyManager) App (CarPropertyManager) App (CarPropertyManager) CarService (订阅管理器) VHAL (VehicleHalClient) 物理传感器 / 执行器 订阅请求 属性变化通知

你看,整个链路其实就两条线:

  • 蓝色线(订阅请求):从上往下,应用告诉 VHAL「我要关注这个属性」。
  • 红色线(变化通知):从下往上,VHAL 把变化推给所有订阅者。

13.5 避坑指南:我踩过的三个坑

最后分享几个实战经验,都是真金白银换来的:

坑一:属性 ID 冲突

我曾经在项目里同时用了两个供应商的 VHAL 实现,结果他们用了同一个属性 ID 表示不同的东西。车速变成了发动机转速,你敢信?

解决方案:所有自定义属性必须走 0x2000 以上的厂商范围,并且统一登记管理。

坑二:订阅了不存在的属性

应用层订阅了一个属性,但 VHAL 根本没实现。结果回调永远不触发,应用一直显示「加载中」。

解决方案:订阅前先调用 getAvailableProperties() 确认一下。

坑三:通知频率过高导致 ANR

有些传感器(比如方向盘转角)变化非常快,每秒几百次。如果每个变化都通知,应用根本处理不过来。

解决方案:VHAL 端做去重和降频,或者应用端设置合理的采样率。

13.6 小结

好了,这一章的内容就这些。总结一下:

  • 属性定义是基础,ID、类型、区域、权限一个都不能错
  • 订阅机制是桥梁,谁关心什么,自己来登记
  • 通知机制是核心,异步、去重、降频是三个关键点

嗯,VHAL 属性系统说白了就是「定义好、登记好、通知好」。把这三件事做好,整个车辆数据通道就稳了。

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