3、Android系统架构与车载定制:Android Automotive OS简介、系统服务层(SystemUI、CarService)、车载应用权限管理
好,咱们进入第三章。说实话,这一章是很多车载开发新手最容易「翻车」的地方。为什么?因为大家习惯了手机上的 Android,一上来就按手机那套逻辑写车机代码,结果跑起来各种诡异——要么界面卡死,要么权限弹窗满天飞,要么 CarService 根本连不上。
嗯,我在项目里见过太多次了。所以这一章,咱们把 Android Automotive OS 的底裤扒开,看看它到底跟手机 Android 有什么不一样。
3.1 Android Automotive OS 到底是什么?
先给个结论:Android Automotive OS 不是 Android Auto。
Android Auto 是手机投屏方案,你的手机是大脑,车机只是个显示器。而 Android Automotive OS 是完整的、跑在车机硬件上的操作系统。说白了,它就是一套专门为汽车定制的 Android。
我记得第一次接触这个项目时,客户问:「能不能直接把手机上的地图 App 装到车机上?」我当时的回答是:「能装,但你会后悔的。」为什么?因为手机 App 没有考虑车载场景——没有驾驶模式、没有硬件按键适配、没有车辆信号交互。你想想看,一个地图 App 在手机上可以随便横竖屏切换,但在车机上,它必须遵守车载的显示规范。
Android Automotive OS 的核心改动,我列一下:
- 系统服务层:新增了 CarService,这是车载特有的系统服务
- UI 框架:SystemUI 被大幅改造,加入了状态栏、导航栏的车载版本
- 权限模型:新增了车辆相关的权限,比如控制空调、读取车速
- 硬件抽象:通过 Vehicle HAL 与车辆硬件通信
重要概念:Android Automotive OS 基于 AOSP(Android Open Source Project),但 Google 提供了 GAS(Google Automotive Services)作为可选的服务套件。如果你的车机要过 Google 认证,就必须用 GAS。
3.2 系统服务层:SystemUI 和 CarService
这一节是重点。车载 Launcher 不是孤立的,它要跟系统服务层深度交互。
3.2.1 SystemUI 的车载改造
手机上的 SystemUI 负责状态栏、通知栏、导航栏。到了车机上,这些东西全变了。
举个例子:手机状态栏显示信号强度、电量、时间。车机状态栏呢?除了这些,还要显示空调温度、座椅加热状态、驾驶模式(D/N/R/P)。而且车机没有「返回键」和「最近任务键」,导航栏只有 Home 键和空调快捷入口。
我在项目中遇到过一个问题:客户要求状态栏显示「当前车速」。我一开始想当然地通过 LocationManager 获取速度,结果发现车机静止时速度不准。后来才知道,车速必须从 CAN 总线读取,通过 Vehicle HAL 上报给 CarService,再分发给 SystemUI。这个链路,你写 Launcher 时根本不用操心,但理解它有助于你调试。
SystemUI 在车载上的关键改动:
- 状态栏:支持自定义图标区域,车辆信号通过 CarService 推送
- 导航栏:去掉了返回键,增加了「空调」「驾驶模式」等快捷入口
- 通知栏:支持驾驶模式下的免打扰策略
- 锁屏:车机通常没有锁屏,或者只有「欢迎界面」
个人建议:如果你要自定义 SystemUI 的状态栏图标,不要直接修改 SystemUI 源码。更好的做法是通过 CarService 的 API 注册一个「车辆信号监听器」,然后通过广播或 binder 通知 SystemUI 更新。这样解耦,后续升级也方便。
3.2.2 CarService:车载应用的后台管家
CarService 是 Android Automotive OS 新增的核心系统服务。它运行在 system_server 进程中,负责管理车辆硬件和车载应用的生命周期。
说白了,CarService 就是车机世界的「交通警察」。它管三件事:
- 车辆硬件访问:通过 Vehicle HAL 读取车速、车门状态、空调温度等
- 应用生命周期管理:决定哪个 App 可以显示在屏幕上(比如导航 App 可以抢占前台)
- 驾驶模式管理:车辆行驶时,限制某些 App 的操作(比如禁止视频播放)
来看一段 CarService 的典型使用代码。假设你的 Launcher 要显示当前车速:
// 获取 Car 对象
Car car = Car.createCar(context);
// 连接 CarService
car.connect(new Car.CarServiceLifecycleListener() {
@Override
public void onConnected(Car car) {
// 获取车辆属性管理器
CarPropertyManager propertyManager =
(CarPropertyManager) car.getCarManager(Car.PROPERTY_SERVICE);
// 订阅车速变化
propertyManager.registerCallback(
new CarPropertyManager.CarPropertyEventCallback() {
@Override
public void onChangeEvent(CarPropertyValue value) {
if (value.getPropertyId() ==
VehiclePropertyIds.PERF_VEHICLE_SPEED) {
float speed = (float) value.getValue();
// 更新 UI
runOnUiThread(() -> updateSpeedUI(speed));
}
}
@Override
public void onErrorEvent(int propertyId, int zone) {
// 处理错误
}
},
VehiclePropertyIds.PERF_VEHICLE_SPEED,
CarPropertyManager.SENSOR_RATE_NORMAL
);
}
@Override
public void onDisconnected(Car car) {
// 处理断开连接
}
});
注意:CarService 的连接是异步的。我曾经见过新手直接在 onCreate 里调用 car.connect(),然后立刻去读车速,结果返回 null。正确的做法是在 onConnected 回调里做所有操作。另外,记得在 onDestroy 里调用 car.disconnect(),否则会内存泄漏。
3.3 车载应用权限管理
这一节,咱们聊聊权限。车载应用的权限模型跟手机有很大不同。
手机上的权限,比如「读取联系人」「访问相机」,都是用户主动授权的。但在车机上,很多权限是「隐式授权」的——因为用户不可能在开车时去点「允许」按钮。
Android Automotive OS 引入了几个车载特有的权限:
| 权限名称 | 保护级别 | 用途 |
|---|---|---|
| android.car.permission.CAR_SPEED | signature|privileged | 读取车速信息 |
| android.car.permission.CAR_ENERGY | signature|privileged | 读取电池/燃油信息 |
| android.car.permission.CAR_CLIMATE | signature|privileged | 控制空调系统 |
| android.car.permission.CAR_INFO | signature|privileged | 读取车辆 VIN、配置等 |
| android.car.permission.CAR_NAVIGATION_MANAGER | signature|privileged | 管理导航应用 |
你发现没有?这些权限的保护级别都是 signature|privileged。这意味着什么?
- signature:只有跟系统签名相同的 App 才能申请。普通第三方 App 根本拿不到。
- privileged:只有预置在 system/priv-app 目录下的 App 才能申请。
所以,你的 Launcher 如果要读取车速,必须满足两个条件:一是用系统签名编译,二是安装在 priv-app 目录下。嗯,这就是为什么车载 Launcher 通常是系统级应用,不能随便从应用商店下载。
避坑指南:我曾经在项目里犯过一个低级错误——在 AndroidManifest.xml 里声明了 CAR_SPEED 权限,但忘了把 App 放到 priv-app 目录。结果运行时一直报 SecurityException。排查了半天才发现是权限等级不够。所以,如果你在调试时遇到「Permission Denied」,先检查你的 App 是不是 priv-app。
另外,还有一个特殊的权限叫 android.car.permission.CAR_DRIVING_STATE。这个权限用来获取当前驾驶状态(停车、行驶、倒车等)。为什么重要?因为很多车载 UI 规范要求:车辆行驶时,禁止显示视频、禁止键盘输入、禁止长文本阅读。你的 Launcher 必须根据驾驶状态动态调整 UI。
举个例子:
// 获取驾驶状态管理器
CarDrivingStateManager drivingStateManager =
(CarDrivingStateManager) car.getCarManager(Car.DRIVING_STATE_SERVICE);
// 注册监听
drivingStateManager.registerListener(new CarDrivingStateEventCallback() {
@Override
public void onDrivingStateChanged(CarDrivingStateEvent event) {
switch (event.getEventValue()) {
case CarDrivingStateEvent.DRIVING_STATE_PARKED:
// 停车状态,可以显示所有内容
enableFullUI();
break;
case CarDrivingStateEvent.DRIVING_STATE_MOVING:
// 行驶状态,限制某些操作
disableVideoPlayback();
disableKeyboardInput();
break;
case CarDrivingStateEvent.DRIVING_STATE_REVERSING:
// 倒车状态,显示倒车影像
showRearCameraView();
break;
}
}
});
这里要注意:驾驶状态的变化是异步的,而且不同车型的触发时机可能不同。有些车是检测到档位变化,有些是检测到车轮转动。所以,不要假设「车辆启动后立刻进入行驶状态」,最好在 onDrivingStateChanged 里做 UI 切换。
3.4 小结
这一章的内容,说白了就是让你明白:车载 Android 不是手机 Android 的简单移植。SystemUI 改了,CarService 是新增的,权限模型也完全不同。
我个人觉得,理解这些底层架构,比学会写几个 UI 控件更重要。因为你在开发 Launcher 时,遇到的 80% 的坑都跟这些系统服务有关。比如:
- 为什么我的 App 收不到车速广播?——权限不够
- 为什么我的 App 在行驶时被强制退出?——驾驶状态限制
- 为什么我的 App 无法控制空调?——没有 CAR_CLIMATE 权限
嗯,下一章咱们就开始动手写 Launcher 的代码了。到时候你会发现,理解了这些底层知识,写代码就像开了上帝视角——你知道每个 API 背后发生了什么。
好,这一章就到这儿。有问题随时交流。