
1. 别把车载系统当成“大号Android平板”很多刚转过来做车载的Android工程师第一反应是“不就是个Android系统在车机上跑嘛多几个App而已”。这个误解的成本非常高。真正的Android车载系统AAOS从架构层面就和手机Android分道扬镳了它是为“车辆是一台行驶中的设备”这个前提设计的有额外的安全分区、HAL层、服务组件还有一个解决座舱与车辆ECU通信的核心服务——CarService。它处理的不只是UI还有车辆状态、电源管理、传感器事件、音频焦点策略这些和数据安全强相关的东西。这个系列第1章我大概聊过整个车载系统的整体形态这章我们聚焦在启动这一件事上。把一个车载娱乐域控制器从硬件上电开始到SystemServer启动再到CarService拉起最后车机屏幕上可以交互整个过程比手机复杂在哪一个核心原因车辆场景对启动有时间约束。传统燃油车还稍微好点新能源车座舱普遍要求冷启动到主界面可交互的时间尽量在3到8秒内否则用户从点火到挂挡出发车机还是黑屏体验就是灾难。而CarService的启动阻塞在主界面绘制之前还是之后、VehicleHal有没有给到合法的车速和挡位信号、电源状态能不能切到正常模式这些都会直接决定“车机到底多久才能用”。所以我们这章不聊应用层怎么开发就来把启动这条链路彻底捋一遍从底层到服务从原理到实际调试。2. 从按下启动键到CarService拉起整个启动链路是怎么串起来的2.1 上电到Bootloader比手机多出来的启动分区逻辑车载域控制器一般挂在整车的12V常电或者KL15点火信号上。上电之后第一步还是走SoC自己的BootROM比如高通8155/8295这类芯片有单独的引导流程。Bootloader阶段和手机的最大区别在于AB分区机制用得比较多很多车厂为了OTA不掉点都会做slot A和slot B的双分区启动的时候通过misc分区里的slot信息决定引导哪个系统。这个阶段如果出了问题一般的现象是设备反复重启或者卡在Logo。调试手段不多基本靠串口log和硬件复位信号这里不展开。2.2 kernel到Android用户态init.rc里多了哪些车载服务Kernel起来之后挂载根文件系统然后执行第一个用户态进程init。这时候车载系统与手机系统开始分叉。除了标准Android的zygote、servicemanager、surfaceflinger这些车载平台上init.rc会额外启动一堆hal服务包括audio、graphics最重要的一个是vehicle hal。以AIDL版本的VHAL为例现在的定义在android.hardware.automotive.vehicle这个package下。VHAL进程会去访问底层的CAN总线、以太网或者私有协议把车速、转速、转向灯、电源模式这些车辆原始信号包装成Android标准属性。注意VHAL这个进程在init阶段就要启动而且它在CarService连接之前必须就绪否则后面系统会一直拿不到车辆数据整个座舱就处于“盲开”状态。2.3 Zygote和SystemServerCarService到底在哪一步被拉起的Zygote起来之后会孵化出system_server进程。Standard Android里SystemServer依次启动了各种服务ActivityManager、PackageManager、WindowManager等等。在AAOS里多了一个特殊的服务叫CarServiceHelperService。这里有一个很多新人搞不清的概念CarService本身并不是跑在system_server进程里的它有一个独立的系统进程com.android.car。CarServiceHelperService这个类的作用是作为system_server里的代理它在SystemServer的startOtherServices()阶段被实例化然后通过SystemServiceHelper.startCarService()去启动真正的CarService进程。为什么会这样设计原因很简单CarService不应该因为system_server崩溃就直接挂掉反过来CarService挂掉也不应该直接拖垮整个system_server。车载系统里这两个进程有各自的职责边界CarService内部崩溃后系统还能尝试重新启动它而system_server继续兜底运行。CarService启动之后它的onStart()里会做一件关键的事情连接VehicleHal。它通过ServiceManager去获取android.hardware.automotive.vehicle.IVehicle这个服务拿到Binder句柄之后创建VehicleHal对象然后注册属性回调、启动一个属性分发的事件循环。到了这里CarService才真正和车辆信号接通。看一份简化的时序init ├── vehicle hal 启动等待HAL ready ├── zygote │ └── system_server │ ├── startOtherServices() │ │ └── CarServiceHelperService.startCarService() │ │ └── 绑定 com.android.car 进程 │ │ ├── CarService.onStart() │ │ │ ├── 获取 IVehicle Binder │ │ │ ├── VehicleHal 初始化 │ │ │ ├── 注册属性订阅 │ │ │ └── 通知 SystemServer CarService 就绪 │ │ └── CarService.onBootPhase() └── Launcher 启动有一件事必须强调CarService启动完成后各种车控App并不能马上开始查询车辆属性。因为在CarService内部还有一个“初始化完成”的门控。VehicleHal连接成功后它会异步等待车辆属性中的电源模式AP_POWER_STATE或者点火状态进入正常态然后才向系统宣告CarService完全可用。这就是为什么很多应用用CarPropertyManager注册监听却在一开始收不到回调——因为VHAL里还没上报有效数据。2.4 CarService的启动阶段和手机App的onCreate完全不是一回事我在带新人时经常被问“CarService的onStart里是不是可以随便做耗时操作”答案是不行。虽然它在独立进程但Binder调用方大量来自system_server和其他系统UI组件如果onStart里阻塞太久会造成整车系统的属性请求全部堆积最终表现为车机性能明显卡顿。实际上CarService内部的启动分了好几个boot phase它在onBootPhase()里逐级推进从PHASE_THIRD_PARTY_APPS_CAN_START到PHASE_BOOT_COMPLETED每一层才放行一部分功能。这样设计是为了避免启动高峰期的资源竞争也是CarService稳定性的关键设计。3. CarService内部从VehicleHal到属性分发的核心机制CarService是所有车辆属性和车辆控制命令的中枢网关。理解它的内部结构对排查启动问题、做功能开发都有很大帮助。3.1 它到底管理了哪些东西按Android Automotive的架构划分CarService内部核心组件大致有这几个CarPropertyService负责车辆属性的增删改查、订阅通知。它是应用层CarPropertyManager对外的承载服务。VehicleHal负责与底层VHAL通信包括把上层的set请求转为IVehicle的set命令、把底层上报的property事件转为CarPropertyService可分发的事件。CarPowerManagementService管理电源模式包括正常的启动态、休眠态、关机态这也是车载系统区别于手机的重要服务。CarAudioService处理音频焦点、音量分组、音区driver zone vs passenger zone。CarClusterService仪表盘相关处理与液晶仪表的通信和投影。CarInputService座舱内的物理按键、旋钮事件。每个子服务都对应一个独立的Binder服务在CarService内部通过car_service_config.xml或者代码注册的方式把服务暴露出去。3.2 一个属性从车端到App端的完整链路这是全章最重要的模型。拿车速信号举例底层ECU通过CAN发出车速信号。VHAL进程内的CAN解析模块读取报文转换成一个VehiclePropValue。VHAL通过回调事件把属性值传给CarService进程里的VehicleHal。VehicleHal解析事件通过属性回调监听器交给CarPropertyService。CarPropertyService根据订阅列表找到注册了这个属性监听的App进程通过CarPropertyManager.CarPropertyEventCallback回调给App。反过来App要设置一个属性比如控制车窗流程就是反向走App调用CarPropertyManager.setProperty()。Binder调用到CarPropertyService。CarPropertyService检查权限和属性可写性。调用VehicleHal.setProperty()。最终写入VHAL由VHAL转成车辆总线命令。这个链路虽然逻辑上简单但实际工程里的坑非常多。比如属性权限的定义VehiclePropertyAccess和VehiclePropertyChangeMode这两个枚举值必须配置正确。一个只读属性你在App里写它CarService会直接丢一个SecurityException而不是等到HAL层才报错。还有一个常见的性能陷阱是属性订阅粒度。新手容易用CarPropertyManager.registerCallback()监听一大堆属性比如把车速、转速、油量、里程全部放在一个回调里。这会导致CarService里的回调线程每次批量触发轻则卡顿重则回调风暴。正确做法是精确订阅你关心的属性ID并且用subscribePropertyEvents替代老版本的全局callback。3.3 VehicleHal的HIDL/AIDL差异迁移时要注意从Android 10开始Google就在推VHAL从HIDL往AIDL迁移。HIDL版本的VHAL接口名是android.hardware.automotive.vehicle2.0::IVehicleAIDL版本直接在android.hardware.automotive.vehicle.IVehicle。如果你在一个基于Android 13的平台上开发新代码一定要用AIDL接口因为这不仅关系到编译依赖还关系到系统对属性定义的支持范围。AIDL版本的VHAL一个明显变化是属性ID从整数枚举变成了VehicleProperty这个VintfStability注解的String定义也就是说属性ID有了更好的扩展性。实际使用中如果遇到老代码用了数字ID而新框架不识别需要在vehicle_property_configuration.asproto之类的地方核对一下版本定义。3.4 CarService崩溃后是如何恢复的有经验的系统工程师都会关心这个问题。CarService进程被system_server盯着它崩溃以后SystemServer里的CarServiceHelperService会收到onCarServiceCrash()回调然后选择重启CarService进程。重启之后CarService重新连接VHAL重新初始化所有属性重新分发一次READ_ONLY属性和部分关键属性的初始值。这里有一个需要注意的恢复问题重启后的CarService会做一次“属性重放”把车辆当前状态重新发给所有还在注册的App监听者。但顶层App如果是在CarService重启过程中调用CarPropertyManager获取属性可能会遇到Binder DeadObjectException。你的App代码里一定要对异常做重试处理而不是让应用直接崩溃。做车机的同学应该都遇到过车机在行驶途中CarService微妙抖动的情况这时候如果导航App崩了体验就很糟糕。4. 启动期最常见的崩溃现场与我的排查思路4.1 现场一一直开机动画Launcher迟迟不出这是最高频的启动问题。排查思路不要一上来就怀疑Launcher先要确认CarService到底起来没有。用工具箱里的老招数adb shell dumpsys activity service com.android.car或者看看系统日志adb logcat -s CarService:V VehicleHal:V CarServiceHelperService:V如果日志里卡在Waiting for vehicle hal这种状态问题的根源基本在VHAL。VHAL进程没起来、Binder注册失败、属性配置解析错误都会导致CarService初始化挂起。我去年排查过一个8155平台案例VHAL服务确实起了但因为它依赖的某个配置文件里写了一个不存在的VehicleArea枚举值导致HAL初始化时抛了IllegalArgumentException整个HAL进程退出CarService就永远等不到它。这种问题logcat里会出现类似:E VehicleHal: Failed to parse property config, error: invalid area E VehicleHal: Configuration error, abort!处理方案仔细检查HAL进程的early log确认配置文件的schema和当前系统版本匹配。另外值得一提的是现在很多车型的HAL进程本身会做WaitForReady的握手如果HAL依赖的域控制器信号比如电源模式没起来它也会一直等。4.2 现场二CarService崩溃并持续重启另一种常见现象是logcat里看到了CarService的崩溃栈然后system_server尝试重新拉起反复几次都失败。崩溃栈一般会指向某些第三方依赖库或so库加载失败。有一个非常经典的坑在Android 12以上系统对动态链接库的白名单管控更严了如果没有正确配置llndk或者vndk相关定义CarService进程在加载so的时候会报类似dlopen failed: library libfoo.so not found的错误。这种问题有个隐蔽点直接用adb shell启动时可能正常因为shell用户的搜索路径和system进程不一样所以在CarService里加载就要多留个心眼。4.3 现场三属性请求直接SecurityExceptionApp崩溃很多App级开发者容易踩这个坑。CarPropertyManager获取到Car对象之后按道理应该再检查一下CarPropertyManager是不是isPropertyAvailable()或者isPropertySupported()。不少代码直接跳过这个检查在CarService还没有把某些属性标记为可用之前就发起请求自然就会抛异常。这类问题严格来说不是启动链路的问题但启动初期出现频率特别高——因为属性初始化有延迟。这里我强烈建议App层做一个封装封装一个CarPropertyManager的同步包装器内部维护一个已支持属性列表每次query之前先查缓存缓存没更新就做一次阻塞查询这样能大大减少启动阶段的异常率。4.4 排查CarService启动问题时的通用命令清单实践中我常备这些命令分享出来命令用途adb shell dumpsys activity service com.android.car查看CarService整体状态、绑定的客户端列表adb shell dumpsys car_service查看CarService内部属性缓存、监听者信息adb shell service list | grep vehicle确认VHAL Binder服务是否注册adb shell dumpsys android.hardware.automotive.vehicle.IVehicle/default查看VHAL侧属性配置与状态adb logcat -b crash查看Java崩溃栈adb logcat -b all -s CarService VehicleHal CarServiceHelperService看关键服务日志toybox top -H -p pid查看CarService线程CPU占用排查启动高峰性能问题这些命令配合使用比我当年靠感觉去猜问题靠谱太多了。5. 车载启动优化的几个关键手段含一点工程经验5.1 把启动时间压在3秒内靠什么做车载系统优化的人都知道系统启动时间是关键指标。CarService的启动优化本质上是一个资源分配问题。首先看时序拓扑哪个服务启动最慢就先把它的前置依赖提前。这里我提一个比较实用的方法将CarService中不需要立即初始化的子模块延后。比如CarBluetoothService、CarRadioService这些非关键服务完全可以在CarService主流程跑完之后延迟初始化。Android提供的机制是SystemService.setBootPhase()之后利用后台线程池或者Handler.postDelayed去初始化。但注意不是所有服务都能延迟电源管理、属性服务和VHAL连接是必须同步完成的。第二个常用优化是VHAL的快速直连。默认情况下CarService起来之后才去ServiceManager里找VHAL如果HAL进程还没注册就需要等待几秒的Binder重试。这里可以改成一个方案在init阶段让VHAL进程early_启动优先级提到zygote之前这样CarService一跑到连接逻辑Binder就能立刻拿到底层接口。5.2 减少属性配置解析的开销VHAL和CarService都各自要解析属性配置文件。有些项目的配置特别庞大每个属性带一堆权限、区域、变化模式的描述解析过程中会产生大量对象分配在低端平台上这一下几十毫秒就没了。优化思路是把配置文件里最常用的属性单独建立一个快速索引在配置解析时同时构建MapInteger, PropertyConfig这样后续属性查询就是O(1)的别每次遍历全部属性。5.3 启动阶段避免让所有App同时注册属性回调车机开机之后桌面、导航、天气、音乐一堆App同时启动同时去注册属性监听。这时候CarService进程内瞬间涌进大量Binder调用如果每个属性回调都要走一遍完整的事件分发流程不仅CPU吃紧binder线程池也容易占满。我见过一个平台因为同时有20多个App注册属性监听CarService直接卡了800ms主线程动画直接掉帧。工程上的做法是做一个“启动风暴保护”在CarService的onBootPhase进入PHASE_BOOT_COMPLETED之前对所有第三方App的registerCallback请求统一排队延迟到启动完成后一起分发。系统PHASE_BOOT_COMPLETED之前本来就不应该允许大量App访问车辆数据这样既保证启动流畅又不影响功能完整性。6. 你还需要理解VHAL的启动时序与整车状态的关系最后这一块比较容易被忽略但恰恰是车载系统区别于手机的地方——VHAL不是只要进程活着就行它必须等整车网络信号就绪才能上报数据。很多专项测试中车机启动很快但CarService会卡在等待有效电源模式这一步。整个点火到CarService正常的完整状态机大概是这样的域控制器上电 - Bootloader - Kernel。VHAL进程启动尝试连接CAN总线或者以太网。在整车还没完成网络唤醒时总线可能是静默的。CarService启动连接VHAL拿到AP_POWER_STATE属性。如果读到的是ON表示车辆已经正常运行如果读到的是SLEEP或者没有读到就要保持等待状态。等到电源模式更新为正常后CarService正式向外广播CarServiceHelperService.SERVICE_NOT_READY到SERVICE_READY的状态变更。因此CarService自己代码里的超时机制就特别重要。官方实现里对等待VHAL上报数据有一个超时时间超时后会继续启动但会标记部分属性为“未知”状态。这样做的目的是宁可让车机先亮屏也不能一直黑屏等着数据。如果你做的是平台开发我给你一个建议在有条件的情况下在HAL层或者域控制器侧准备一个“快速上报”模式即在点火信号到来时立刻把关键属性车速、挡位、电源模式、油量/电量上报一次不必等总线全部唤醒。这个小小的改动能把CarService的就绪时间往前提不少。7. 一个真实的踩坑复盘CarService启动到一半卡死的调查过程分享一个近期帮朋友排查的案例非常有代表性。项目场景是某新平台第一版软件系统启动后车机屏幕能亮但一直停在开机LogoLauncher出不来了。用adb logcat -s CarServiceHelperService:V查看看到CarServiceHelperService: startCarService CarServiceHelperService: CarService connected CarServiceHelperService: CarService is not readyCarServiceHelperService已经连上了CarService但CarService一直没有通知就绪。然后查CarService进程日志CarService: init start VehicleHal: connect to vehicle hal VehicleHal: waiting for vehicle hal to ready一直卡在waiting for vehicle hal。然后用service list | grep vehicle查看发现VHAL服务压根没有注册。再往前查VHAL进程日志发现了这样的异常E VehicleHal: property 289407744 is defined inconfig but not supported by hardware F VehicleHal: abort due to property config mismatch也就是说VHAL进程在解析属性配置时发现配置文件里的某个属性硬件不支持直接调用了abort()导致整个HAL进程退出。为什么配置文件里会有硬件不支持的属性因为软件是上一代平台移植过来的配置里带了旧车型特有的属性比如燃油车油箱盖状态新平台的硬件根本不响应这个属性。这种问题第一眼根本看不出来行业惯例但本质上就是“配置漂移”。处理方式对照新平台的vehicle_property_configuration.asproto和物理可用属性列表把配置文件里无效的属性全部清理掉。这个案例告诉我们CarService启动问题可以牵涉到最底层的HAL配置排查时不能只盯service层要把链路当成整体来看。8. 给正在做车载系统的你几个工程建议虽然这一章主要内容是启动和CarService但最后我还是想结合自己带项目的体会说几个可能对你有用的建议。第一车载平台的HAL层一定要有完整的自检能力。HAL进程启动之后先自己检查硬件状态、总线状态和配置合法性。如果配置不合法不要直接abort而是打好日志、进入一个降级模式等上层诊断模块来查。一个直接abort的HAL会让整个车的娱乐系统都起不来这是不能接受的。第二CarService的核心路径保持精简。不要在CarService的主流程里加太多业务逻辑尤其是那些和车辆属性无关的第三方代码。它的角色就是“车辆状态中枢”把属性管好、把电源状态机跑顺就非常够了。其他所有业务功能都应该用独立的service应用去做。第三App开发要把CarService的连接态当成和网络一样的非稳定资源来对待。开发车载应用的时候默认所有属性请求都有可能失败、默认所有回调都有可能延迟并且做好重试和降级这是最稳的写法。第四工具链值得提前投入。一台带串口输出的调试板、一份完整的logcat过滤配置、一个可以实时监测VHAL上报数据的脚本这些工具能在你排查问题的时候省下一天的精力。后续我会单独写一篇关于车载调试工具箱的分享把这些脚本和命令整理出来。这一章的内容到这里基本讲完了。我始终觉得搞明白CarService和启动时序是进入车载Android深水区的入场券。下一章我们可以聊一聊CarPropertyManager的应用开发细节以及如何正确地在App里使用车辆属性API。