3. Android Automotive OS:AAOS架构、HAL层、CarService、多用户管理

好,我们进入第三章。这一章要聊的是AAOS——Android Automotive OS。说白了,就是车机版的Android。

很多人觉得AAOS就是把手机Android改一改,塞进车里。嗯,这么理解也没全错,但你要是真这么干,项目大概率会翻车。我见过不少团队,拿着手机端的开发经验直接怼到车机上,结果发现连最基本的车载功能都调不通。

AAOS不是简单的“换皮”。它从架构层面就做了大量针对车载场景的改造。今天我们就把它拆开看看。

3.1 AAOS整体架构

先看一张图,把AAOS的层次结构理清楚。

AAOS 分层架构图 应用层 (Application Layer) OEM 自研 App | 第三方 App | 系统 App (Launcher, Settings, Phone) 应用框架层 (Application Framework) CarService | ActivityManager | WindowManager | PackageManager 系统服务层 (System Services) Vehicle HAL | Audio HAL | Camera HAL | CarPropertyService 硬件抽象层 (HAL) Vehicle HAL (VHAL) | Audio HAL | Camera HAL | Sensors HAL 内核层 (Linux Kernel) CAN 驱动 | GPIO 驱动 | I2C/SPI 驱动 | 显示驱动 | 电源管理 AAOS 在标准 Android 架构基础上,增加了 Vehicle HAL 和 CarService 两个核心组件

从下往上看,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的切换流程是这样的:

  1. 当前用户点击“切换用户”
  2. 系统保存当前用户的状态(座椅位置、音量、导航等)
  3. 系统加载目标用户的配置
  4. 恢复目标用户上次的使用状态
  5. 切换完成,界面更新

整个过程应该是无缝的,用户感觉不到明显的卡顿。我在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负责把硬件能力暴露给应用层。多用户管理则是车载场景的刚需,每个用户的数据隔离、无缝切换,这些都是量产项目必须做好的。