7、CarService 与 VHAL 的通信:Binder 与 Vendor 扩展,属性订阅与事件上报机制
好,咱们今天聊点硬核的。CarService 和 VHAL 怎么说话?说白了,就是 Android 系统上层应用和底层硬件之间的那条“电话线”。这条线要是断了,车机上的空调、车窗、车门就全成了摆设。我在做第一个车载项目时,就吃过这个亏——当时属性上报延迟了 200ms,结果客户试驾时直接说“这车反应太慢了”。嗯,从那以后,我对这块的每一个细节都不敢马虎。
7.1 通信基石:Binder 的妙用
CarService 跑在系统进程里,VHAL 跑在 Vendor 分区。它们之间怎么通信?答案是 Binder。Android 的 Binder 机制,说白了就是一套进程间通信(IPC)的框架。你想想看,两个进程不能直接访问对方的内存,那就得靠 Binder 来“传话”。
在 AAOS 里,CarService 通过 IVehicle 这个 Binder 接口来调用 VHAL。这个接口定义了一堆方法,比如 get()、set()、subscribe() 等等。VHAL 那边则实现了这些方法,然后通过 Binder 驱动把结果传回来。
核心要点:Binder 在这里不仅仅是传数据,它还负责权限校验。CarService 调用 VHAL 时,Binder 会检查调用方的 UID 和 PID,确保只有系统服务才能操作车辆属性。这一点在安全上至关重要。
7.2 Vendor 扩展:别被标准属性绑死
Google 定义了一套标准的车辆属性,比如 VEHICLE_PROPERTY_HVAC_TEMPERATURE_SET 这种。但实际项目中,车厂总有自己私有的功能——比如“座椅按摩强度”、“香氛浓度”、“氛围灯颜色”。这些标准属性里没有,怎么办?
Vendor 扩展就是干这个的。VHAL 允许你定义自己的属性 ID,范围在 0x1000 - 0xFFFF 之间。我建议你把这些自定义属性统一放在一个头文件里,方便管理。比如:
// vendor_ext_properties.h
#define VENDOR_PROPERTY_SEAT_MASSAGE_LEVEL 0x1001
#define VENDOR_PROPERTY_FRAGRANCE_CONCENTRATION 0x1002
#define VENDOR_PROPERTY_AMBIENT_LIGHT_COLOR 0x1003
然后你在 VHAL 的实现里,对这些属性做对应的处理。CarService 那边通过 get() 和 set() 来读写,跟标准属性完全一样。嗯,这里要注意:Vendor 扩展属性的权限模型需要你自己定义。我曾经见过一个项目,把座椅按摩的写权限放给了第三方应用,结果用户乱调导致硬件过载——这锅可不小。
避坑指南:Vendor 扩展属性的 ID 不要跟标准属性冲突。标准属性范围是 0x0000 - 0x0FFF,你从 0x1000 开始用就对了。另外,建议在属性定义里加上版本号,方便后续升级。
7.3 属性订阅:别轮询,要推送
早期做车载系统时,有人喜欢用轮询——每隔 100ms 去读一次车速。这方法太傻了。CPU 空转、功耗高、还容易丢数据。AAOS 提供了属性订阅机制,让 VHAL 在属性变化时主动推给 CarService。
订阅的流程很简单:
- CarService 调用
subscribe(),传入属性 ID 和回调接口 - VHAL 记录这个订阅关系
- 当属性值变化时,VHAL 通过回调把新值推过来
- CarService 收到后,再分发给对应的应用
我个人习惯把订阅逻辑封装成一个管理类,这样代码更清晰。比如:
class VehiclePropertySubscriber {
public:
void subscribe(int32_t propId, float sampleRate) {
// 调用 VHAL 的 subscribe 接口
mVehicleHal->subscribe(propId, sampleRate);
// 记录订阅状态
mSubscribedProps.insert(propId);
}
void onPropertyEvent(const VehiclePropValue& value) {
// 收到事件后,分发给对应的回调
auto it = mCallbacks.find(value.prop);
if (it != mCallbacks.end()) {
it->second(value);
}
}
private:
std::set<int32_t> mSubscribedProps;
std::map<int32_t, std::function<void(const VehiclePropValue&)>> mCallbacks;
};
小技巧:订阅时可以指定采样率。比如车速属性,你每秒更新 10 次就够了;但方向盘转角,可能需要每秒 100 次。别一股脑全设成最高频率,否则 VHAL 那边压力会很大。
7.4 事件上报机制:VHAL 的“主动汇报”
属性订阅是 CarService 主动发起的,但有些场景下,VHAL 需要主动上报事件——比如“车门未关”、“安全带未系”、“电池电量过低”。这些事件不是 CarService 主动订阅的,而是 VHAL 检测到异常后主动推送的。
事件上报的流程是这样的:
- VHAL 检测到某个属性变化(比如车门状态从“关”变成“开”)
- VHAL 调用
onPropertyEvent()回调 - CarService 收到后,判断是否需要通知用户
- 如果需要,CarService 通过 HMI 显示警告信息
这里有个关键点:事件上报的频率不能太高。我曾经遇到过一个项目,VHAL 每秒上报 1000 次“电池电压波动”事件,结果 CarService 那边的消息队列直接爆了。后来我们加了一个防抖机制——同一属性在 100ms 内只上报一次。
经验之谈:事件上报的优先级也很重要。安全相关的事件(比如刹车故障)应该走高优先级通道,而娱乐相关的事件(比如音乐切换)可以走普通通道。AAOS 的 VHAL 接口里有一个 VehiclePropertyStatus 字段,你可以用它来标记事件的紧急程度。
7.5 整体通信流程:一张图看懂
说了这么多,咱们用一张图来总结一下 CarService 和 VHAL 的通信流程。这张图我画了好几次才满意,希望能帮你理清思路。
7.6 总结
CarService 和 VHAL 的通信,说白了就是 Binder 加属性订阅再加事件上报。Binder 是通道,属性订阅是“拉”模式,事件上报是“推”模式。两者结合,才能让车机系统既高效又安全。
我个人觉得,理解这块内容的关键在于“分层”。CarService 只管业务逻辑,VHAL 只管硬件操作,中间通过 Binder 解耦。这样设计的好处是,换硬件时只需要改 VHAL,CarService 基本不用动。嗯,这就是 AAOS 架构的精妙之处。
最后提醒一句:调试 VHAL 通信时,记得打开 Binder 的日志。命令是 adb shell dumpsys vehicle,能看到当前有哪些订阅、最近的事件上报记录。这个命令我几乎每天都要用,强烈推荐。