ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Android 13车机CPMS启动流程全解析:从VHAL到电源状态机

Android 13车机CPMS启动流程全解析:从VHAL到电源状态机 上周帮人排查一个第三方车机固件的问题设备通电开机一切正常但熄火停车五分钟后就再也唤不醒了。日志翻下来最后所有的矛头都指向了 CarPowerManagementService以下简称 CPMS的启动链路——准确说是它初始化还没完成时VHAL 上报的车辆电源状态已经丢了导致状态机停在一个永远等不到下一个事件的节点上。这件事让我觉得凡是做 Android 车载系统、SystemUI 定制、车机电源策略移植的人CPMS 的启动流程都应该是一条绕不开的主线。这篇就以 Android 13 源码为基准从 Bootloader 一路拆到 CPMS 状态机就绪把谁创建它、它依赖谁、初始化了哪些东西、如何验证和排障一次性讲透。1. CPMS 解决的是车辆电信号与Android 系统状态之间的翻译问题先说清楚 CPMS 到底在整辆车里扮演什么角色否则直接扎进代码很容易被绕晕。手机这类普通设备的电源管理逻辑非常直接用户按一下电源键PMIC 上电Bootloader 起来系统开始跑再按一下系统走 sleep 或者关机流程。整个触发动作来自人的手指Android 的 PowerManagerService 只需要感知到按键事件就够了。但车机完全不同。车机上电不是靠 Android 系统自己控制的而是整车电气系统在决定拧钥匙、踩刹车点火、车门解锁、充电桩连接都会让 12V、ACC、IGN 这些电源轨发生变化。Android 系统必须学会看车辆的信号来动作。VHALVehicle Hardware Abstraction Layer把这些物理电信号抽象成了一个核心属性POWER_STATE。车辆端的电源状态被枚举成五档车辆状态值含义典型场景EMERGENCY_OFF紧急断电碰撞、异常OFF整车下电锁车离开ACCESSORY附件模式未点火但音响、窗户可用ON整车通电仪表点亮电机/发动机待命START点火启动动力系统工作CPMS 就挂在 VHAL 之上。它监听到车辆状态从 OFF 变成 ACCESSORY、ON、START就知道该让 Android 进入正常唤醒反过来监听到车辆从 ON 一路掉回 OFF就要决定系统是进入休眠、延迟关机还是立刻关机。这个决定就是整套车机电源管理的核心。但 CPMS 不仅是翻译它还管着哪些东西可以用。车机在 ON 和 OFF 状态下允许运行的模块是完全不同的。比如车辆即将下电时显示器和音频应该先断电但网络模块可能要再撑几秒用来上报状态而在 ON_DISCHARGE熄火但人还在车里这种特殊状态下中控屏还要继续亮着车门没锁时甚至系统不能睡。这些策略全部由 CPMS 通过 CarPowerPolicy 下发到 SystemUI、音频服务、网络服务等模块。所以说它是裁判员背后靠的就是这套策略分发机制。一句话搞懂 CPMS 启动流程本质上是搞懂从车辆给电到 Android 系统完成电源管理接管这个窗口期里每一步到底发生了什么。这个窗口期任何一环断了车机开机后都会出现异常行为甚至根本无法进入正常工作状态。2. 开机链路从 Bootloader 到 CarService 之间的必经节点这一节先把全局时间线拉出来方便确认 CPMS 在整个启动链路上到底处于多晚的阶段。车机的启动链路和普通嵌入式设备区别不大依次是 Bootloader、Kernel、init、Zygote、SystemServer、CarService。很多做上层应用的人一听到 Bootloader 就头大但只需要抓住一个关键结论CPMS 是这条链上最晚被初始化的一批模块之一它所有行为都建立在 Android 系统服务已经可用的前提上。Bootloader车机上绝大多数是 u-boot 或者定制版本负责最基础的内存、存储、显示初始化然后把内核镜像加载进 DDR。内核起来后挂载文件系统启动用户空间的第一个进程 init。init 解析 ueventd 和 zygote 启动脚本拉起 Zygote。Zygote fork 出 SystemServerSystemServer 才真正开始逐项启动 Java 层的系统服务。也就是从这一刻起Android 才具备所谓的服务化能力。SystemServer 里和车机最相关的动作发生在启动系统服务的尾段。它通过PackageManager.hasSystemFeature(FEATURE_AUTOMOTIVE)判断当前设备是否是车载设备只有是车载设备才会调用 SystemServiceManager 启动CarServiceHelperService。这个判断依靠的是设备上的android.software.automotive.xml特性声明。第三方固件移植时如果把这个特性写错了CarService 根本不会被拉起整台车机在 Android 看来就是一台普通平板电源管理退化为按键模式。CarServiceHelperService 本身跑在 system_server 进程里它干的事情非常纯粹绑定或者拉起包名为com.android.car的 CarService 进程。CarService 和系统服务不在同一个进程这并非随意设计。车载业务非常容易因为某个监听器、某个厂商扩展代码崩溃如果直接放在 system_server 里一个小小的异常就可能把整个 Android 系统拖死。隔离成独立进程后即使 CarService 崩了SystemServer 依然可以把这个进程重新拉起来至少保证系统 UI 还在、用户不会变砖。CarService 启动后onCreate里会实例化核心的CarServiceImpl真正的初始化逻辑都在CarServiceImpl.onStart()和随后的 initialize 中。到这一步CPMS 的创建时机才到来。后面的时序一句话概括就是SystemServer 通过 Feature 判断决定要不要拉车机服务CarServiceHelperService 把 CarService 进程唤醒CarServiceImpl 在 initialize 阶段创建 CPMS 并触发初始化。任何一层没有按预期跑CPMS 都不可能正常启动。接入 MCU 方案的盒子型车机这里还要多提一句。很多产品是 MCU Android SoC 双芯结构MCU 管理物理电源时序SoC 通过串口、GPIO 或者内部虚拟设备与 MCU 通信。VHAL 里拿到的 POWER_STATE 本质上来自这颗 MCU。如果 MCU 侧的电源状态机设计不合理或者 SoC 和 MCU 之间的通信时序没对齐CPMS 收到的状态就会错乱或者缺失。后面排查问题的时候要把这条隐藏链路也放进脑子里。3. CarServiceImpl 如何给 CPMS 凑齐衣食父母CarServiceImpl的 initialize 方法里服务创建顺序是有讲究的。CPMS 不是自己想启动就启动的它构造时依赖一堆别的服务这些依赖必须先于它创建好否则初始化必然半路抛异常。先看几个最关键依赖依赖职责为什么 CPMS 需要它VehicleHal封装 VHAL Binder 通信订阅车辆电源状态、向 HAL 回写 AP 状态CarPropertyService属性订阅分发监听 POWER_STATE 及其他与电源相关的车身属性CarUserService多用户管理关机阶段需要判断当前用户、保留用户状态CarPackageManagerService包管理判定哪些应用有关机前延寿或阻止关机的权限PowerManager 相关 Binder系统电源接口执行 goToSleep、WakeLock、shutdown这里要特别解释两个容易被忽略的点。第一为什么 CPMS 不直接用 CarPropertyService 收电源变化而是单独在 VehicleHal 上注册一个电源事件监听。原因是电源事件和普通车身属性事件在语义上并不等价。电源事件通常带着额外参数比如车辆请求系统进入某种更高优先级状态的原因而且电源变化事件的时序要求很高一旦延迟上报系统可能已经误入休眠。所以代码上给了一条独立的、轻量的通路避免被属性总线上的其他慢逻辑阻塞。第二CarUserService 必须在 CPMS 之前创建的原因更直接。CPMS 在启动时需要感知当前用户状态因为关机准备阶段要决定用户 0 的进程要不要保留当前前台用户的 Surface 要不要冻结。如果 CarUserService 还没准备就绪CPMS 拿不到用户列表初始化阶段就会失败。这在源码里表现为一条硬依赖。在 AOSP 里CarServiceImpl 做完这些服务的创建后会 new 出CarPowerManagementService把上面这些依赖通过构造方法传进去然后紧接着调用它的init()。一个常见的误解是创建完就完事了实际上 CPMS 的大量关键工作都落在 init 里。构造函数只是把依赖引用存起来真正向 VHAL 注册监听、初始化状态机、加载策略的逻辑都在 init 中。这也是很多人在阅读源码时容易忽略的你以为看到一个对象被 new 出来就代表服务起来了其实还差一个 init 的步骤。顺便说一句第三方固件或者车机方案商在做裁剪时最容易出的问题就是停掉某个看似不相关的服务来省资源。但只要动了 CarUserService 或者 CarPackageManagerService 的启动顺序CPMS 很快会在开机日志里抛出一串找不到依赖的异常最终表现为系统起来后完全不受车辆电源控制。遇到这类情况第一步永远回去核对服务创建顺序。4. CPMS 的 init 到底干了哪四件事CarPowerManagementService.init()是整篇分析的重头戏。这段代码几乎决定了车机在接下来整个生命周期里的电源表现。拆开来看初始化工作主要汇聚成四个方向。4.1 接管系统电源状态通知通道init 的第一步是调用 VHAL 侧的电源事件注册接口把自己挂成一个 PowerEventListener。这个注册和 CarPropertyService 的监听不同它直接影响 VHAL 是否会把后续的电源状态变化转发给 Android。这里有一个关键动作takeSystemPowerState可以理解成 Android 向 VHAL 宣告我已经准备好接收电源状态事件了。为什么这个动作重要因为 VHAL 侧的车辆电源事件不是广播式的。HAL 实现方通常会先确认 Android 是否已经就绪再决定要不要把状态变化传上来。如果 CPMS 迟迟没有接管VHAL 可能只缓存最近一次状态或者在内部直接把电源事件丢弃。文章开头提到的那个熄火后无法唤醒问题本质就是 AP 侧接管时机太晚车辆 OFF 事件没有送达状态机永远停在 ON电源管理自然失效。4.2 加载电源策略配置CPMS 不是自己在代码里写死哪个组件在哪个状态能用而是从power_policy配置里读出来的。这份配置在 AOSP 中由PowerPolicyProvider负责解析内容大致是定义每个 PowerComponent 在 ON、SUSPEND、SHUTDOWN 各阶段的可访问状态。PowerComponent 包括 AUDIO、DISPLAY、BLUETOOTH、WIFI、NFC、PROJECTION、SYSTEM 等模块。配置的作用可以简单理解成一张矩阵表横向是电源状态纵向是组件每个格子里写着 allowed 或者 not allowed。系统在状态迁移时CPMS 会根据这张表批量通知所有订阅了 CarPowerPolicy 的模块让它们自己调整行为。这块最坑的是配置错误的现象极具迷惑性。比如某个固件把 DISPLAY 在 ON 状态下的策略设成了 disabled开机后系统日志一切正常车辆状态也是 ON但屏幕就是黑的。排查的人如果只盯代码很难发现根源在 XML 配置。所以做车机的人一定要把 power_policy 配置当成代码一样 review上线前的测试清单里也必须包含各电源状态下的模块启停验证。4.3 初始化 CarPowerStateMachine 状态机CPMS 本身不是一个简单的回调集合它内部维护了一个专门的状态机对象。状态机负责承载所有的电源状态转换逻辑包括超时处理、状态进入/退出的钩子、和 PowerManager 的交互等。状态机初始化的关键点是在创建后立刻与当前车辆状态做同步。如果初始化完成时车辆已经处于 ON 或者 START状态机应当直接进入 ON 态让系统保持唤醒如果车辆处于 OFF状态机可能进入一个等待车辆的初始态并且配合策略决定要不要立刻请求休眠。这个初始同步非常容易出问题CPMS 启动时如果因为某些时序原因还没收到 VHAL 缓存的当前车辆状态状态机就会默认选择一个保守状态可能导致屏幕永远点亮也可能导致系统过早休眠。在实际车辆中上电瞬间和 Android 启动完成之间往往隔着好几秒车辆早就处于 ON 了。所以状态机初始化时通常存在一次主动查询当前车辆状态的逻辑而不是干等下一次事件上报。这也是为什么调试时看到 init 日志之后紧接着会有一条读取当前 POWER_STATE 的操作记录。4.4 注册系统级电源动作回调最后一步是把 CPMS 和 system_server 里的 PowerManager 能力关联起来。车机应用进程不能直接拿系统进程的 PowerManagerService 实例但可以通过公开的 PowerManager Binder 接口调用 goToSleep、WakeLock 等方法。CPMS 初始化时会准备好这些引用同时把内部的关机通知回调注册给相关模块比如 ShutdownListener、SuspendListener。还有一类非常重要的注册项是系统广播和电池状态听感。关机不是 CPMS 单方面能完成的事情它需要把 SHUTDOWN_PREPARE、SHUTDOWN_ENTER 等广播发出去让所有应用有机会保存状态、上报数据。Android 13 对这些广播的阶段划分比老版本更细应用如果没按阶段实现就会在关机时出现数据丢失。做系统定制的人通常会在 init 完成后立刻检查这些广播监听是否被注册成功确保上层应用能收到完整的关机时序。5. Android 13 状态机语义启动完成后系统到底该停在哪个状态很多新手把 CPMS 的启动流程理解成服务起来就完事实际上服务起来只代表初始化完成系统最终会停在哪个电源状态取决于状态机和车辆信号的匹配结果。Android 13 里状态机的状态集合比老版本丰富得多。列举几个关键状态它们都在启动流程中可能出现状态触发条件系统表现WAIT_FOR_VEHICLE_ON初始化完成但车辆未上电屏幕熄灭等待车辆上电事件ON车辆处于 ON/START系统完全唤醒业务正常运行ON_DISCHARGE车辆 OFF 但用户仍在车内屏幕可能保持一段时间的亮屏延迟关机WAIT_FOR_VEHICLE_OFF车辆请求下电系统准备进入休眠或关机SHUTDOWN_PREPARE进入关机流程第一阶段广播通知应用保存数据SHUTDOWN_ENTER所有准备完成执行实际关机SUSPEND_ENTER车辆下电且允许休眠系统进入深度睡眠WAIT_FOR_AP_STANDBYAP 进入待机等待等待下次车辆上电唤醒Android 13 相对上个版本比较显著的变化体现在 ON_DISCHARGE 和 WAIT_FOR_AP_STANDBY 这两个状态的交互上。以前的逻辑比较粗暴车辆 OFF 就立刻考虑关机和休眠。但实际用车场景中车主可能只是临时熄火人还在车里听歌这时系统立刻关机体验非常差。ON_DISCHARGE 就是为了这种场景存在的车辆 OFF 后系统切换到蓄电池供电模式屏幕和音频可以按照策略继续工作一段时间等检测到用户离开或者超时再进入真正的休眠或关机。启动流程里还有一个经常被上层开发忽略的组件——CarPowerPolicyFilter。SystemUI 或者其他应用可以通过 CarPowerPolicyManager 注册一个 PolicyFilter指定自己关心哪些组件和哪些状态。状态变化时CPMS 通过 CarPowerPolicyManager 把新的 CarPowerPolicy 推送给所有匹配的订阅者。SystemUI 的定制工作很多时候就是在根据这个策略切换自己的亮屏、息屏 UI。所以移植 Android 13 SystemUI 到车机上如果不接好 CarPowerPolicy 的订阅屏幕行为和电源状态就会脱节。状态机的最终状态直接决定了车机用户的第一体验。理想情况下车辆启动后状态机应该快速收敛到 ON让系统所有能力可用。如果固件或者 VHAL 有问题状态机可能在 WAIT_FOR_VEHICLE_ON 上卡住表现出来就是系统启动了但屏幕一直黑着、按啥都没反应。这时候别怀疑底层硬件坏了先返回去看 CPMS 当前停在哪个状态再反推是哪个事件没送到。6. 实战验证怎么确认 CPMS 真的启动到位了纸上谈兵到这一步该说说实际操作了。我在排查电源问题时有一套固定的验证路径效率比瞎猜高得多。6.1 先看 dumpsys确认服务对象存在且状态正确CarService 进程支持 dump在 adb 里执行adb shell dumpsys car_service输出内容很长用关键字过滤adb shell dumpsys car_service | grep -i -A 40 CarPowerManagementService正常启动的车机这里会列出当前电源状态、状态机状态、注册的监听器数量和已下发的策略。重点关注两项当前 PowerState 是否和车辆实际状态一致监听器列表里是否有 SystemUI 等关键模块。如果输出里根本没有 CPMS 这一段说明服务初始化没走完基本可以断定是在 init 阶段抛了异常。接着检查 VHAL 侧的真实车辆状态adb shell dumpsys android.hardware.automotive.vehicle.IVehicle/default这里能看到 VHAL 上报的 POWER_STATE 当前值。把 dumpsys car_service 里的状态和这里的车辆状态对比就能快速判断到底是 CPMS 没收到信号还是收到了信号但状态机没正确转换。6.2 抓 logcat确认初始化时序CPMS 的关键日志 TAG 包括CarPowerManagementService、CarPowerStateMachine、CarService、VehicleHal。过滤这几个 TAG 重新开机adb logcat -c adb logcat -s CarPowerManagementService CarPowerStateMachine CarService VehicleHal一条正常启动流程里应当能依次看到 CarServiceImpl 启动、CPMS 创建、init 调用、VHAL 电源事件注册、状态机同步初始状态这几类日志。哪一步缺失就往哪一步对应的代码和配置去查。比如看不到 VHAL 上报的车辆状态优先怀疑 HAL 层而不是 CPMS。需要注意的是车载设备的 logcat 默认可能不开 verbose 级别厂商发布版固件经常会过滤掉 power 相关的详细日志。排查时最好选择 userdebug 或者 eng 版本或者直接用 root 权限临时修改日志级别否则日志量不足以支撑分析。6.3 常见的坑和对应解法按我的经验CPMS 启动问题集中在下面几类第一类是开机后马上休眠或关机。这通常不是 CPMS 逻辑出错而是 VHAL 上报的初始车辆状态就是 OFF或者初始状态查询超时状态机进入了安全性的保守分支。处理方法是先在 VHAL 侧确认上电后 POWER_STATE 有没有第一时间上报再确认 CPMS 初始化时是否发生了主动查询。如果两者都没有问题再看 power_policy 是否把某些关键组件在 OFF 态下配置成可用导致系统误以为还在正常工作。第二类是开机后黑屏但有系统日志。这很大概率是 power_policy 把 DISPLAY 组件在 ON 状态下配错了或者 SystemUI 没有正确响应 CarPowerPolicy 的变化。用 dumpsys 查看当前已下发的策略内容核对 DISPLAY 的状态即可定位。第三类是第三方 Android 13 固件移植后完全不受车辆电源控制。很多移动设备或者盒子刷了车机固件但 VHAL 根本没有实现 POWER_STATE 相关的属性CPMS 永远等不到有效事件只能停在初始态。这类问题在移植固件中非常常见解法也直接要么在 VHAL 实现层把 POWER_STATE 模拟出来要么在系统服务启动条件上把 FEATURE_AUTOMOTIVE 关掉让设备回到普通 Android 电源逻辑二选一。6.4 一个大幅提高调试效率的小技巧如果在真车上反复测试电源时序每次都要拧钥匙、熄火等待效率其实很低。我个人习惯让硬件工程师或者 VHAL 负责人在 VHAL 实现里留一个调试入口比如通过 hidl/vhal property 写入的方式动态修改 POWER_STATE 的值模拟 OFF、ACC、ON、START 的切换。这样在电脑前就能随时制造电源事件配合 logcat 复现各种问题速度会快非常多。还有一个辅助手段是看 SystemUI 层的行为。Android 13 车机 SystemUI 对电源策略的响应非常敏感屏幕息掉、亮起、进入屏保都对应不同的 CarPowerPolicy。如果 SystemUI 日志里频繁出现策略相关的错误回调说明应用层与 CPMS 之间的订阅链路有断点优先检查注册 PolicyFilter 时使用的 PowerComponent 是否和系统配置一致。对我来说CPMS 的启动流程分析从来不是看一个服务的 new 和 init 那么简单。它牵扯到 VHAL 的时序、系统服务的依赖顺序、策略配置的准确性、上层应用的订阅机制任何一个环节脱节都会以电源异常的形式暴露出来。而排障最忌讳的就是一上来就怀疑 CPMS 本身有 bug。大部分问题最终都出在它收到的信号不对或者它下发的策略没人正确执行。只要沿着VHAL 状态 → CPMS 状态机 → CarPowerPolicy 下发 → 各模块响应这条链路逐段核对多数疑难问题都能被准确定位。
返回列表