3. Android Automotive OS:AAOS架构、HAL层、CarService、多用户管理
好,我们进入第三章。这一章要聊的是AAOS——Android Automotive OS。说白了,就是车机版的Android。
很多人觉得AAOS就是把手机Android改一改,塞进车里。嗯,这么理解也没全错,但你要是真这么干,项目大概率会翻车。我见过不少团队,拿着手机端的开发经验直接怼到车机上,结果发现连最基本的车载功能都调不通。
AAOS不是简单的“换皮”。它从架构层面就做了大量针对车载场景的改造。今天我们就把它拆开看看。
3.1 AAOS整体架构
先看一张图,把AAOS的层次结构理清楚。
从下往上看,AAOS和普通Android最大的区别在中间两层:HAL层和系统服务层。普通Android的HAL管的是摄像头、传感器、音频这些。AAOS多了一个——Vehicle HAL,专门管车。
再往上,系统服务层里多了个CarService。它是车载功能的总调度中心。应用层想获取车速、空调温度、方向盘状态,都得通过CarService去问VHAL。
核心要点:AAOS = 标准Android + Vehicle HAL + CarService。这两个组件是车载开发的门槛,也是你接下来要啃的硬骨头。
3.2 HAL层:Vehicle HAL
HAL的全称是Hardware Abstraction Layer。名字很学术,说白了就是“硬件驱动”的标准化接口。
你想想看,8155芯片要对接的车身网络五花八门。有的车用CAN总线,有的用LIN,有的甚至还在用K线。如果没有一个统一的抽象层,上层应用每适配一款车就得重写一遍代码。那项目就别想交付了。
Vehicle HAL就是来解决这个问题的。它定义了一套标准接口,上层只管调用,底层驱动负责实现。
3.2.1 VHAL的核心概念
VHAL里最重要的两个东西:Property和Area。
| 概念 | 说明 | 举例 |
|---|---|---|
| Property | 车辆的一个属性或状态 | 车速、续航里程、空调温度、车门状态 |
| Area | 属性的作用区域 | 左前车窗、右后车窗、主驾座椅、副驾座椅 |
| VehiclePropValue | 属性值的数据结构 | int32、float、string、bytes |
| VehiclePropConfig | 属性的配置信息 | 读写权限、变化模式、支持的区域 |
举个例子。你要获取当前车速,VHAL里对应的Property是VEHICLE_PROPERTY_PERF_VEHICLE_SPEED。CarService通过这个ID去VHAL里查,VHAL再通过CAN总线从车身ECU拿到数据,返回给上层。
我在项目里遇到过一个问题:某款车的车速信号不是标准的CAN报文,而是通过私有协议走的。当时VHAL的默认实现根本读不到数据。最后我们只能自己写了一个VHAL扩展,在底层把私有协议转成标准Property。嗯,这种坑在量产项目里很常见。
3.2.2 VHAL的接口定义
VHAL的核心接口定义在hardware/interfaces/automotive/vehicle/目录下。我们看一个简化版的HIDL定义:
// IVehicle.hal
interface IVehicle {
// 获取所有支持的属性列表
getAllProperties() generates (vec<VehiclePropConfig> propConfigs);
// 获取指定属性的当前值
get(VehiclePropKey propId, vec<int32_t> areas)
generates (StatusCode status, vec<VehiclePropValue> values);
// 设置属性值
set(VehiclePropValue value)
generates (StatusCode status);
// 订阅属性变化
subscribe(IVehicleCallback callback, vec<SubscribeOptions> options)
generates (StatusCode status);
// 取消订阅
unsubscribe(IVehicleCallback callback, vec<SubscribeOptions> options)
generates (StatusCode status);
};
注意看subscribe接口。车载场景里,很多数据是持续变化的,比如车速、转速、电池电量。如果上层应用一直轮询,CPU开销太大。VHAL支持订阅机制——你告诉VHAL“我要关注车速变化”,VHAL在数据变化时主动推给你。
实战建议:在8155平台上,VHAL的订阅频率不要设得太高。我曾经见过有人把车速订阅设成10ms一次,结果CAN总线负载直接飙到80%,导致其他报文丢帧。一般来说,100ms的更新频率对大多数车载场景已经足够了。
3.3 CarService:车载功能的总调度
CarService是AAOS里最核心的系统服务。它运行在com.android.car进程里,是应用层和VHAL之间的桥梁。
你可以把CarService理解成一个“翻译官”。应用层说“我要开空调”,CarService把它翻译成VHAL能懂的Property设置指令。反过来,VHAL上报“车速变了”,CarService把它翻译成应用层能接收的广播或回调。
3.3.1 CarService的内部结构
CarService内部由多个子服务组成,每个子服务负责一类车载功能:
- CarPropertyService:管理车辆属性,最核心的服务
- CarAudioService:管理车载音频路由、音量、音效
- CarInputService:处理方向盘按键、旋钮等物理输入
- CarHvacService:管理空调系统
- CarDrivingStateService:管理驾驶状态(停车、行驶、倒车)
- CarDiagnosticService:提供车辆诊断信息
这些子服务都继承自CarServiceBase,由CarService统一管理生命周期。
3.3.2 应用如何访问CarService
应用层通过Car类来获取CarService的代理。看一段代码:
// 获取Car实例
Car car = Car.createCar(context);
// 连接CarService
car.connect(new Car.CarServiceLifecycleListener() {
@Override
public void onConnected(Car car) {
// 获取CarPropertyManager
CarPropertyManager propertyManager =
(CarPropertyManager) car.getCarManager(Car.PROPERTY_SERVICE);
// 读取车速
CarPropertyValue<Float> speedValue =
propertyManager.getProperty(
VehiclePropertyIds.PERF_VEHICLE_SPEED, 0);
float speed = speedValue.getValue();
// 订阅车速变化
propertyManager.registerCallback(
new CarPropertyEventCallback() {
@Override
public void onChangeEvent(CarPropertyValue value) {
// 车速变化了,更新UI
updateSpeedUI(value.getValue());
}
@Override
public void onErrorEvent(int propertyId, int zone) {
Log.e("CarDemo", "Property error: " + propertyId);
}
},
VehiclePropertyIds.PERF_VEHICLE_SPEED,
CarPropertyManager.SENSOR_RATE_NORMAL);
}
@Override
public void onDisconnected(Car car) {
// CarService挂了,做清理工作
}
});
这段代码展示了三个关键步骤:连接CarService、读取属性、订阅变化。你在8155上开发车载应用,基本天天都要写类似的代码。
注意:CarService的连接是异步的。不要在onCreate里直接调用car.getCarManager(),那样会返回null。一定要等onConnected回调触发后再操作。这个坑我踩过不止一次。
3.4 多用户管理
车和手机不一样。手机基本是单人设备,车是多人共享的。今天你开,明天你老婆开,后天可能借给朋友开。每个人的座椅位置、后视镜角度、音乐偏好、导航历史都不一样。
AAOS的多用户管理就是来解决这个问题的。
3.4.1 用户类型
AAOS定义了三种用户类型:
| 用户类型 | 说明 | 典型场景 |
|---|---|---|
| 系统用户 (System User) | 系统内置用户,不可删除 | 运行系统服务、后台任务 |
| 管理员用户 (Admin User) | 拥有最高权限,可管理其他用户 | 车主、车辆管理员 |
| 普通用户 (Regular User) | 受限用户,只能使用自己的数据 | 家庭成员、临时驾驶员 |
系统用户是隐藏的,普通用户看不到。管理员用户通常是第一个创建的用户,可以添加/删除其他用户。普通用户只能管理自己的设置和数据。
3.4.2 用户切换流程
AAOS的用户切换和手机不太一样。手机上切换用户会锁屏,车机上不能这样——你总不能让驾驶员在高速上切换用户然后锁屏吧?
AAOS的切换流程是这样的:
- 当前用户点击“切换用户”
- 系统保存当前用户的状态(座椅位置、音量、导航等)
- 系统加载目标用户的配置
- 恢复目标用户上次的使用状态
- 切换完成,界面更新
整个过程应该是无缝的,用户感觉不到明显的卡顿。我在8155上实测过,从点击切换到界面更新完成,大概需要1.5秒。这个时间主要花在应用重启上——每个用户的应用数据是隔离的,切换用户意味着要杀掉当前用户的所有应用,再启动目标用户的应用。
3.4.3 多用户下的数据隔离
AAOS为每个用户创建独立的用户数据目录:
/data/user/0/ -- 系统用户
/data/user/10/ -- 管理员用户
/data/user/11/ -- 普通用户1
/data/user/12/ -- 普通用户2
每个用户的应用只能访问自己目录下的数据。这就保证了隐私安全——你老婆登录她的账号,看不到你的导航历史记录。
但有些数据是需要共享的。比如车辆设置(空调温度、驾驶模式),这些不应该跟着用户走。AAOS通过共享存储区域来解决:
/data/user_de/0/ -- 设备加密存储,所有用户可读
CarService的配置数据就存在共享区域里。这样不管哪个用户登录,空调温度都能保持一致。
开发建议:在开发车载应用时,一定要想清楚哪些数据是用户私有的,哪些是设备共享的。我见过一个第三方导航App,把“是否开启实时路况”这个设置存在了用户私有目录里。结果用户A关了路况,切换到用户B,路况又自动打开了。用户B一脸懵,以为是App坏了。
3.5 本章小结
AAOS的架构,说白了就是标准Android加了两样东西:Vehicle HAL和CarService。VHAL负责和车身硬件打交道,CarService负责把硬件能力暴露给应用层。多用户管理则是车载场景的刚需,每个用户的数据隔离、无缝切换,这些都是量产项目必须做好的。