30、Android Automotive与车机系统:车载架构、CarService、AAOS与多屏交互

说实话,做车载系统开发跟做手机开发完全是两码事。手机你只管一个屏幕,车机呢?仪表盘、中控、副驾屏、后排屏、HUD……光屏幕就能让你头皮发麻。我最早接触Android Automotive时,第一反应是:“这不就是个定制ROM吗?”后来被现实狠狠教育了一顿。

今天咱们就聊聊Android Automotive(简称AAOS)的核心。说白了,它跟手机上的Android是同一个爹,但架构上做了大量针对车的改造。你想想看,车机不能死机、不能卡顿、还得跟CAN总线、以太网、蓝牙钥匙这些硬件打交道——这复杂度,比手机高了一个量级。

车载架构的整体分层

先看一张我手绘的架构图,把AAOS的层次理清楚:

应用层(车载App) 导航 | 音乐 | 空调控制 | 车辆设置 | 仪表盘 CarService(核心服务层) 车辆属性管理 | 驾驶模式 | 空调/门窗/灯光控制 Vehicle HAL(硬件抽象层) CAN总线接口 | 以太网 | 传感器 | 执行器 Linux Kernel + 车载驱动 CAN驱动 | 显示驱动 | 音频驱动 | 电源管理 多屏交互与窗口管理 DisplayManager | WindowManager | SurfaceFlinger

这张图我画了好几次才满意。你看,从底层到顶层,每一层都有明确的职责。最关键的其实是中间那层——CarService。它就像车机的“大脑”,所有App想控制车辆功能,都得通过它。

CarService:车机的灵魂

CarService是什么?说白了,它是一个系统级服务,运行在system_server进程里。它负责管理车辆的所有属性——车速、电量、空调温度、车门状态……这些在AAOS里都叫“Vehicle Property”。

我记得第一次调试CarService时,遇到一个坑:空调温度设置后,实际出风口温度没变化。查了半天,发现是HAL层返回的property ID跟CarService里定义的不一致。嗯,这种“对不上号”的问题在车载开发里太常见了。

CarService的核心接口长这样:

// CarService 核心接口示例
public class Car {
    // 获取车辆属性管理器
    public CarPropertyManager getPropertyManager();
    
    // 获取空调控制器
    public CarClimaController getClimaController();
    
    // 获取驾驶模式(停车/行驶/倒车)
    public CarDrivingState getDrivingState();
}

// 实际使用场景
Car car = Car.createCar(context);
CarPropertyManager pm = car.getPropertyManager();
// 读取当前车速
int speed = pm.getInt(VehicleProperty.PERF_VEHICLE_SPEED, 0);
// 设置空调温度
pm.setInt(VehicleProperty.HVAC_TEMPERATURE_SET, 24);
个人经验:CarService的启动顺序很关键。我建议在系统启动初期就初始化CarService,否则App一启动就调用getPropertyManager(),很容易拿到null。曾经有个项目,就因为启动时序问题,导致导航App在开机后10秒内无法获取GPS信号——后来加了个延迟初始化才解决。

AAOS与普通Android的区别

你可能会问:AAOS跟手机Android到底差在哪?我列个表,一目了然:

维度 普通Android AAOS
核心服务 AMS、WMS、PMS + CarService、Vehicle HAL
多屏支持 单屏为主,多屏需定制 原生支持多Display、多Window
电源管理 手机休眠/唤醒 车辆ACC/ON/OFF状态机
安全要求 普通应用沙箱 驾驶安全、功能安全(ISO 26262)
硬件接口 USB、蓝牙、WiFi CAN、LIN、以太网、车载传感器

你看,AAOS多了一个CarService层,而且电源管理完全不一样。手机你按一下电源键就休眠了,车机呢?你得考虑ACC(附件通电)、ON(全车通电)、START(点火)三种状态。我曾经在项目里遇到过:车熄火了,但中控屏还在亮着,因为电源管理没处理好——结果第二天电瓶亏电了。

多屏交互:不止是“多一块屏幕”

多屏交互是AAOS最头疼的地方。你以为就是多开几个窗口?太天真了。车机上的多屏,涉及到Display ID分配、SurfaceFlinger的图层管理、跨屏拖拽、甚至不同屏幕的分辨率和刷新率都不一样。

举个例子:中控屏是1920x720,仪表盘是1280x480,副驾屏是1920x1080。三个屏幕的DPI不同,渲染方式也不同。AAOS里怎么处理?

// 多屏显示管理示例
DisplayManager dm = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
Display[] displays = dm.getDisplays();

for (Display display : displays) {
    int displayId = display.getDisplayId();
    String name = display.getName();
    // 0: 主屏(中控), 1: 仪表盘, 2: 副驾屏
    Log.d("MultiDisplay", "Display " + displayId + ": " + name);
}

// 将Activity显示到指定屏幕
ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchDisplayId(1); // 仪表盘
startActivity(intent, options.toBundle());
避坑指南:我曾经在项目里把导航App强行显示到仪表盘上,结果仪表盘的分辨率太低,导航界面被截断了。后来才意识到:每个屏幕的density和layout配置必须单独处理。建议在AndroidManifest里为不同屏幕指定不同的layout资源。

多屏交互还有一个难点:焦点管理。车机上,用户可能同时操作中控和副驾屏,但系统只有一个焦点。AAOS的做法是:每个Display可以有自己的焦点窗口,但全局输入事件(比如方向盘按键)只能发给当前活跃的Display。这个逻辑我调了整整两天才跑通。

Vehicle HAL:连接硬件与系统的桥梁

Vehicle HAL是AAOS里最底层的Java/Native接口。它定义了车辆属性的读写方式。每个属性都有一个唯一的ID,比如车速是0x15200400,空调温度是0x28400200。这些ID在hardware/libhardware/include/hardware/vehicle.h里定义。

我见过很多开发者直接硬编码这些ID,结果换了个车型就崩了。正确的做法是通过CarPropertyManager的API来查询,而不是直接写死。

// Vehicle HAL 属性定义示例
enum VehicleProperty {
    // 车速 (m/s)
    PERF_VEHICLE_SPEED = 0x15200400,
    // 发动机转速 (rpm)
    PERF_ENGINE_RPM = 0x15200401,
    // 空调设定温度 (摄氏度)
    HVAC_TEMPERATURE_SET = 0x28400200,
    // 车门状态 (0:关, 1:开)
    VEHICLE_DOOR_POS = 0x1A400200,
};

// HAL层实现示例
int32_t VehicleHal::getProperty(VehiclePropValue* propValue) {
    switch (propValue->prop) {
        case PERF_VEHICLE_SPEED:
            // 从CAN总线读取车速
            propValue->value.int32Values[0] = readCanBusSpeed();
            return 0;
        case HVAC_TEMPERATURE_SET:
            // 从空调ECU读取温度
            propValue->value.floatValues[0] = readHvacTemp();
            return 0;
        default:
            return -1;
    }
}
核心要点:Vehicle HAL是C++实现的,通过JNI跟Java层的CarService通信。性能要求高的属性(比如车速、转速)建议走Native回调,避免Java层的GC抖动。我有个项目,车速回调用了Java层,结果每100ms触发一次GC,导致仪表盘指针卡顿——后来改成Native回调就解决了。

实际项目中的坑与经验

最后分享几个我在车载项目里踩过的坑:

  • 属性订阅的频控:不要每10ms就订阅一次车速变化,CarService会崩溃。建议最低50ms间隔。
  • 多屏的SurfaceFlinger配置:每个Display需要独立的HWC(硬件合成器)图层,否则会出现画面撕裂。
  • 电源状态机:车机从ACC切到OFF时,CarService要保存所有属性状态,否则下次启动空调温度会重置。
  • 安全模式:行驶中禁止视频播放,这个逻辑要在CarService里做,而不是App层——因为App可以被绕过。

嗯,车载开发就是这样,细节决定成败。你想想看,手机App崩溃了最多重启一下,车机崩溃了可是要出事故的。所以AAOS的每一个设计,背后都是对安全的极致追求。

如果你正在做车载项目,我建议先从CarService入手,把属性管理、多屏显示、电源状态机这三个模块吃透。其他的,比如音频路由、蓝牙电话、导航投射,都是在这三个基础上扩展的。

好了,今天就聊到这儿。记住:车载系统没有“差不多”,只有“精确到毫秒”。


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