3、Android Automotive系统基础:Android Automotive架构概览、HAL层与AIDL接口、CarService与Vehicle HAL、Android系统服务与驱动交互
好,我们进入第三章。这一章,咱们聊聊Android Automotive的系统基础。
说实话,很多做Linux BSP的兄弟刚转过来时,最懵的就是这个。明明是个Android,怎么跟车打交道的?车载的HAL跟手机HAL有啥区别?CarService又是个什么鬼?
别急,我一个一个给你拆开讲。
3.1 Android Automotive架构概览
先看整体架构。Android Automotive本质上还是Android,但它多了一层专门跟车打交道的框架。
我习惯把整个系统分成四层来看:
- 应用层:包括车载Launcher、设置、空调控制、地图导航等。这些App通过标准Android API和Car API来访问车辆数据。
- 框架层:核心是CarService,它管理所有车辆相关的服务。还有CarPropertyManager、CarSensorManager等API。
- HAL层:Vehicle HAL是专门为汽车定义的硬件抽象层。它定义了车辆属性和信号的标准接口。
- 内核层:驱动、CAN总线、GPIO、I2C等。这部分跟QNX或Linux BSP基本一致。
嗯,这里要注意:Android Automotive的HAL层跟手机Android的HAL有本质区别。手机HAL是面向设备的(Camera、Audio、GPS),而Vehicle HAL是面向信号的(车速、车门状态、空调温度)。
核心观点:Vehicle HAL不是用来控制某个硬件外设的,它是用来收发车辆信号的。这是理解整个架构的关键。
我画了一张架构图,你看一眼就明白了。
3.2 HAL层与AIDL接口
好,咱们深入一层。HAL层在Android Automotive里是怎么定义的?
Android从8.0开始推Treble架构,HAL接口用HIDL定义。到了Android 11之后,Google开始全面转向AIDL。现在的新项目,基本都是AIDL HAL了。
我个人建议:新项目直接上AIDL。别再用HIDL了,那玩意儿迟早被淘汰。
一个典型的Vehicle HAL AIDL接口长这样:
// IVehicle.aidl
package android.hardware.automotive.vehicle;
interface IVehicle {
// 获取所有支持的车辆属性列表
VehiclePropConfig[] getAllPropConfigs();
// 获取指定属性的值
StatusCode get(VehiclePropValue request, inout VehiclePropValue result);
// 设置指定属性的值
StatusCode set(VehiclePropValue value);
// 订阅属性变化
StatusCode subscribe(IVehicleCallback callback,
in VehiclePropValue[] propValues,
float sampleRate);
// 取消订阅
StatusCode unsubscribe(IVehicleCallback callback,
in int[] propIds);
}
你看,这个接口定义得很干净。get/set是同步操作,subscribe/unsubscribe是异步通知。实际项目中,大部分车辆信号都是通过subscribe来获取的,比如车速、转速、车门状态。
实战经验:我在项目中遇到过一个问题——订阅车速信号时,采样率设得太高(100Hz),结果CPU占用率飙升。后来发现,车速信号10Hz就够用了。别盲目追求高采样率,够用就行。
再说说VehiclePropValue这个结构体。它定义了车辆属性的值:
// VehiclePropValue结构体
struct VehiclePropValue {
int32_t propId; // 属性ID,比如车速是0x11100200
int32_t areaId; // 区域ID,比如左前门、右前门
int64_t timestamp; // 时间戳,纳秒
VehiclePropValueType value; // 值类型:int32, float, string, bytes等
int32_t status; // 状态:AVAILABLE, UNAVAILABLE, ERROR
};
嗯,这里要注意propId的编码规则。Google定义了一套标准属性ID,比如:
| 属性名 | 属性ID | 类型 | 说明 |
|---|---|---|---|
| VEHICLE_SPEED | 0x11100200 | FLOAT | 车速,单位km/h |
| ENGINE_RPM | 0x11100300 | FLOAT | 发动机转速 |
| DOOR_POSITION | 0x11400200 | INT32 | 车门位置,0=关闭,1=打开 |
| HVAC_TEMPERATURE_SET | 0x12400100 | FLOAT | 空调设定温度 |
这些ID是Google在hardware/interfaces/automotive/vehicle/里定义好的。你想想看,如果车厂要加自定义属性,怎么办?Google留了厂商自定义范围:0x20000000 ~ 0x2FFFFFFF。
避坑指南:我曾经在项目里直接用了0x20000001作为自定义属性ID,结果跟另一个模块冲突了。后来我们统一在文档里登记所有自定义ID,避免重复。建议你也这么做。
3.3 CarService与Vehicle HAL
CarService是Android Automotive框架层的核心。它运行在system_server进程里,负责管理所有车辆相关的服务。
说白了,CarService就是应用层和HAL层之间的桥梁。它做了三件事:
- 启动时加载Vehicle HAL:通过JNI调用HAL的open()方法,拿到IVehicle接口。
- 管理车辆属性:维护一个属性缓存池,处理get/set请求,管理订阅者列表。
- 权限检查:不是所有App都能读写车辆属性。CarService会检查调用方的权限。
CarService启动流程大致是这样的:
// CarService启动流程(简化版)
1. SystemServer启动CarServiceHelperService
2. CarServiceHelperService启动CarService(独立进程)
3. CarService初始化Vehicle HAL:
- 加载libvehiclenative.so
- 调用VehicleHalClient::init()
- 获取IVehicle HAL接口
4. CarService注册所有标准车辆属性
5. 启动属性订阅循环(轮询或事件驱动)
6. 对外暴露CarPropertyManager等API
我记得第一次看这个流程时,有个地方让我困惑了很久——为什么CarService要单独跑一个进程?后来想明白了:如果CarService跟system_server同进程,一旦CarService崩溃,整个系统就挂了。独立进程可以做到故障隔离。
3.4 Android系统服务与驱动交互
最后,咱们聊聊驱动怎么跟系统服务交互。
在Android Automotive里,驱动跟系统服务的交互路径是这样的:
- 驱动层:通过Linux内核的CAN子系统、GPIO子系统、I2C等,读取硬件信号。
- HAL实现层:Vehicle HAL的实现(so库)通过ioctl、read/write、netlink等方式跟驱动通信。
- HAL接口层:通过AIDL定义的IVehicle接口,把数据传给CarService。
- 框架层:CarService把数据分发给订阅的App。
举个例子,车速信号的完整链路:
// 车速信号链路
CAN总线 → CAN驱动 → can_utils → Vehicle HAL实现 →
IVehicle::get(VEHICLE_SPEED) → CarService →
CarPropertyManager → 仪表盘App显示车速
你想想看,这条链路里哪个环节最容易出问题?
我个人经验是:HAL实现层。因为驱动层和框架层都有成熟的测试工具,但HAL实现层往往是车厂自己写的,bug最多。
调试技巧:当车速信号不准时,先别急着查驱动。用dumpsys car_service命令看看CarService里缓存的车速值是多少。如果缓存值是对的,那就是App的问题;如果缓存值不对,再往下查HAL实现。
还有一个常见问题:驱动上报的数据格式跟HAL定义的不一致。比如驱动上报的车速是km/h的整数,但HAL定义的是float类型。这时候HAL实现层要做单位转换。
我曾经遇到过一个坑:某个车厂的CAN总线车速单位是mph,但HAL定义的是km/h。HAL实现层忘了做转换,结果仪表盘显示的车速比实际快了60%。嗯,这种低级错误,代码审查时一定要盯紧。
好了,这一章的内容就这些。Android Automotive的架构其实不复杂,关键是要理解Vehicle HAL的设计思想——它是面向信号的,不是面向设备的。把这个想通了,后面写驱动、调HAL就顺了。