第三章 Android车机系统架构

好,咱们进入正题。这一章我打算聊聊Android车机系统的整体架构。说实话,很多刚入行的朋友一上来就盯着CAN总线代码看,结果越看越迷糊。为什么?因为你不了解整个系统是怎么搭起来的。

我个人的习惯是,先看地图再走路。Android车机系统说白了就是一个分层结构,每一层各司其职。咱们从下往上捋一遍。

3.1 Android Automotive OS概述

Android Automotive OS,注意不是Android Auto。这两个东西完全不一样。Android Auto是手机投屏方案,而Automotive OS是真正跑在车机硬件上的操作系统。

我记得2017年Google刚推出这个系统时,圈内很多人不看好。觉得Android手机都卡,跑车上能行吗?但几年下来,事实证明这套系统确实能打。它跟普通Android最大的区别在哪?我列几个关键点:

  • 电源管理:车机不能像手机一样关机,得支持深度睡眠和快速唤醒
  • 音频策略:导航、电话、媒体音要能独立控制,互不干扰
  • 硬件抽象:CAN总线、GPS、车载传感器这些都得有专门的HAL层
  • 安全机制:驾驶安全相关的应用优先级最高,不能随便被杀掉

你想想看,普通Android手机死机了重启就行。车机要是死机了,倒车影像出不来,那可不是闹着玩的。所以Automotive OS在稳定性上做了大量定制。

3.2 车机硬件抽象层(HAL)

HAL层,说白了就是硬件和系统之间的翻译官。我刚开始做车机项目时,最头疼的就是这层。为什么?因为每家车厂的硬件都不一样,但系统层代码又不能随便改。

Android Automotive的HAL主要包含这么几个模块:

HAL模块 功能说明 我遇到的坑
Vehicle HAL 车辆属性接口,车速、车门、灯光等 属性ID定义各家不统一
Audio HAL 音频路由和策略控制 多音区混音容易出问题
Camera HAL 车载摄像头,特别是环视 延迟要求极高,丢帧是大忌
Sensors HAL 加速度计、陀螺仪等 数据融合算法要调参

这里重点说说Vehicle HAL。它是车机系统里最核心的HAL,负责跟CAN总线打交道。它的工作流程大概是这样的:

// Vehicle HAL的核心接口
IVehicle* vehicle = getVehicleService();

// 订阅车速信号
vehicle->subscribe(VehicleProperty::PERF_VEHICLE_SPEED);

// 回调处理
void onPropertyEvent(vehicle_prop_value_t* value) {
    // 这里拿到的是经过HAL解析后的数据
    float speed = value->value.floats[0];
    // 嗯,单位是m/s,记得转成km/h
}

我曾经在一个项目里踩过坑。当时车厂提供的CAN协议里,车速信号是16位整型,但HAL层默认当浮点处理。结果车速显示忽快忽慢,查了两天才找到原因。所以做HAL开发时,一定要跟硬件团队确认好数据类型和精度。

3.3 车机服务架构

再往上走,就是服务层了。Android Automotive的服务架构跟普通Android不太一样。它多了几个车机专属的系统服务:

  • CarService:车机服务的总入口,管理所有车辆功能
  • CarPropertyService:车辆属性管理,读写车速、里程等
  • CarAudioService:音频策略管理,控制不同音源的优先级
  • CarNavigationService:导航服务,跟仪表盘交互

这些服务都跑在系统进程中,权限很高。应用层想获取车辆数据,得通过CarService来间接访问。这样做的好处是安全——普通应用不能直接操作硬件。

我建议你在做服务开发时,特别注意两点:

第一,服务启动顺序。 CarService必须在Vehicle HAL初始化完成后才能启动。否则一启动就崩溃,而且崩溃原因很难排查。

第二,线程模型。 车机服务里大量使用Binder通信,回调都是异步的。处理不当容易出并发问题。

3.4 车机应用框架

最上层就是应用层了。Android Automotive的应用框架跟普通Android基本一致,但多了几个车机专用的组件:

// 车机应用的基本结构
public class CarApp extends Activity {
    private Car car;
    private CarPropertyManager propertyManager;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        // 连接CarService
        car = Car.createCar(this);
        car.connect();
        
        // 获取属性管理器
        propertyManager = (CarPropertyManager) 
            car.getCarManager(Car.PROPERTY_SERVICE);
    }
    
    private void getVehicleSpeed() {
        // 读取车速
        CarPropertyValue<Float> speed = 
            propertyManager.getProperty(
                VehicleProperty.PERF_VEHICLE_SPEED, 0);
        
        if (speed != null) {
            float currentSpeed = speed.getValue();
            // 更新UI
        }
    }
}

这里有个重要的设计原则:应用层不能直接调用HAL接口。所有车辆数据的读写,都必须通过CarService中转。这样做是为了保证系统安全性和稳定性。

小技巧: 调试车机应用时,可以用adb命令模拟车辆信号:

# 模拟车速100km/h
adb shell dumpsys car_service inject-property \
    --prop 291 --value 27.78

注意单位是m/s,100km/h约等于27.78m/s。

说到应用框架,我还想提一下UI设计。车机应用的UI跟手机完全不同。驾驶过程中,驾驶员注意力有限,所以Google搞了一套模板化的UI组件:

  • 列表项要够大,方便触控
  • 文字要简洁,一眼能看懂
  • 动画要少,避免分散注意力
  • 关键信息要突出,比如导航提示

警告: 千万不要在车机应用里做复杂的动画或者频繁刷新UI。我曾经见过一个第三方音乐App,播放时封面图转个不停,结果导致系统UI线程卡顿,导航语音都延迟了。这种问题在车机上是非常危险的。

好了,这一章的内容就这些。Android车机系统架构说白了就是四层:操作系统、硬件抽象、系统服务、应用框架。每一层各司其职,层与层之间通过标准接口通信。理解了这些,后面讲CAN总线通信时你就能知道数据是怎么从物理总线一路传到应用界面的。

下一章咱们开始聊CAN总线的基础知识,到时候会涉及一些硬件层面的东西,做好准备。