07 - 配置字 CCP 与常驻监听

本篇讲两个”全局级”机制:① 配置字(CCP,Car Config Property) —— 决定”这台车有没有这个功能、支持到什么程度”;② VehiclePermanentListener —— App 启动时常驻的信号监听器,负责配置字落地和首帧缓存。两者配合解决”打开页面前就能拿到车型能力信息”和”UI 首帧不闪烁”两个问题。


1. 配置字(CCP)是什么

配置字是出厂配置位,描述当前车型的能力。例如:

  • 这台车有没有空气悬架?(SUSPENSION_CONFIG
  • 这台车是两驱还是四驱?(TRANSMISSION_DRIVELINE
  • 这台车有没有哨兵模式?
  • 这台车支持几个座椅记忆位?

配置字与普通信号的区别:

维度配置字(CCP)普通车控信号
变化频率几乎不变(车型决定)实时变化
用途决定功能有无/范围决定功能当前状态
查询入口CarConfigManagerSettingsCarPropertyManager
缓存策略强缓存(Int.MIN_VALUE 哨兵)按 propId 缓存

整机视角(详见整机《车控信号/Android 配置字接入指南》):配置字有三种下发方式——SystemProperty(native property_get)、Settings.Global(MiCarSettings 用这种)、SOA 信号下发(当普通信号订阅)。本项目的 VehiclePermanentListener 收到配置字信号后落地到 Settings.Global,业务侧再通过 CarConfigManager 读取。


2. 配置字查询入口:CarConfigManager

路径:base/settingsVehicleLib/src/main/java/com/android/car/settings/miauto/CarConfigManager.kt

  • 对业务只暴露 isXxxEnable() 形式的方法
  • 默认走缓存;动态属性必须 skipCache = true(参考 L619 isCarModeInShowRoom、L843 getOwnerModeActiveState
  • 有单测:base/settingsVehicleLib/src/test/java/com/android/car/settings/miauto/CarConfigManagerTest.kt

2.1 ⚠️ 哨兵值 Int.MIN_VALUE(关键设计)

CarConfigManager.kt:78getIntProperty 实现用 Int.MIN_VALUE 表示”服务不可用/未取到”:

fun getIntProperty(propId: Int, areaId: Int = 0): Int {
    // 拿不到时返回 Int.MIN_VALUE,而非 0 或 -1
}

CarConfigManagerTest.kt:97-133 对此做了三次验证。新增 CCP 查询时务必理解:判断”配置字未取到”要用 == Int.MIN_VALUE,不能用 == 0<= 0,否则会把合法的 0 值误判为不可用。

2.2 新增配置字查询的标准动作

  1. CarConfigManager.ktisXxxEnable() 方法,默认走缓存
  2. 如果该配置字是动态的(运行时会变),必须 skipCache = true
  3. Int.MIN_VALUE 判断服务不可用
  4. 业务侧调用 CarConfigManager.INSTANCE.isXxxEnable() 即可

3. 常驻监听器:VehiclePermanentListener

路径:base/settingsVehicleLib/src/main/java/com/android/car/settings/miauto/VehiclePermanentListener.java

3.1 职责

进程级单例(L87-96),App 启动时常驻,做四件事:

  1. 订阅一批全局信号:所有 CarConfig 配置字 + 一组业务全局信号(getPropertyIdSet() L281-309,约 30 个 propId,如驾驶模式、车主模式)
  2. 配置字落地:收到配置字信号时写入 Settings.GlobalMiCarSettings.updateConfighandleConfigPropertyChanged L345-360)——这就是 CCP 运行时落地的实现
  3. 进程级缓存:维护 mPropertyValueMap(L71),对外暴露静态 getCachePropertyValue(propId, areaId)(L335-343)。任何 Controller 在 onCreate 时即可拿到首帧,避免 UI 闪烁
  4. 信号拉起 Activity:维护 mPropertyActivityMap / mPropertyPrefKeyMap(L76-77),信号变化时可直接拉起目标 Activity(L401-411)

3.2 启动时机

MainThreadStartTask 调用:

app/src/main/java/com/android/car/settings/miauto/MainThreadStartTask.java:62-67

GlobalCarPropertyManager.init(mApplication);
// ...
VehiclePermanentListener.getInstance().init();
MiCarSettings.setConfigManager(CarConfigManager.INSTANCE);

这一刻触发 LocalCarManager.createCar,CarService 是否 ready 由系统回调通知。

3.3 二级回调

业务方可注册二级回调监听全局信号:

VehiclePermanentListener.getInstance().addCarPropertyManagerCallback(callback);   // L311

3.4 给 Controller 的首帧缓存

CarPropertyMgrPreferenceControlleronCreate 时读 VehiclePermanentListener.getCachePropertyValue(L545)拿到依赖信号初值——这就是”页面还没收到信号上报,但 UI 已经能正确显示”的实现根源。


4. 全局 Manager 入口:GlobalCarPropertyManager

路径:base/settingsVehicleLib/src/main/java/com/android/car/settings/miauto/GlobalCarPropertyManager.kt(L16,object 单例)

  • SoftReference 持有全局 SettingsCarPropertyManager 实例(L18,L23-30 init
  • getCarManager()(L32):CarConfigManager 等非页面逻辑通过它直接读信号
  • addPropertyCallback(ids: ArraySet<Int>, callback) / removePropertyCallback(L40-59):全局多 callback 监听同一信号集

SettingsCarPropertyManager 的区别:后者是 per-Controller 实例(每个 Controller 一个,绑页面生命周期);GlobalCarPropertyManager全局单例,给页面外的逻辑(如 MenuAccountControllerCarConfigManager)用。


5. 全链路:从车端配置字到 UI 显隐

车端配置字(车型能力)
   ↓ SOA 信号上报
VehiclePermanentListener 收到配置字信号
   ├─► 写入 Settings.Global(持久化)
   ├─► MiCarSettings.updateConfig(内存)
   └─► mPropertyValueMap 缓存
        ↓
CarConfigManager.isXxxEnable() 读 Settings.Global / 缓存
        ↓
Fragment.getPreferenceKeyResIdsToRemove()      // 按配置字移除整个 Preference
Controller.getAvailabilityStatus()              // 按配置字控制可用/显隐

6. 关键路径速查

主题路径
配置字查询base/settingsVehicleLib/.../miauto/CarConfigManager.kt
配置字单测base/settingsVehicleLib/src/test/.../CarConfigManagerTest.kt
常驻监听base/settingsVehicleLib/.../miauto/VehiclePermanentListener.java
全局 Managerbase/settingsVehicleLib/.../miauto/GlobalCarPropertyManager.kt
App 启动初始化app/src/main/.../miauto/MainThreadStartTask.java
Settings.Global providerMiCarSettingsSettings.Global key 定义)