02 - 应用层主通道:CarProperty 机制

本篇是 App 内信号传递的核心。讲 settingsVehicleLib 如何在 AOSP CarPropertyManager 之上封装出缓存去重 / 超时监控 / 生命周期托管 / 回调分发收敛四件套,让业务侧只需调 getIntProperty / setCarProperty

所有行号经对抗核验验证准确(截至 2026-07-12 / dev)。


1. 五层封装总览

业务 Controller(业务侧)
        │  getIntProperty() / setVehicleProperty()
        ▼
CarPropertyMgrPreferenceController    (UI 脊梁基类,持有 ⑤)
        │
        ▼
⑤ SettingsCarPropertyManager          (per-Controller,缓存/超时/分发)
        │  内部持有
        ▼
④ VehicleControlManager               (extends NewBaseCarServiceManager<CarPropertyManager>)
        │  委托注册给
        ▼
③ CarPropertyCacheManager             (单例,propId 去重 + 全局缓存)
        │  通过
        ▼
② LocalCarManager                     (Car 单例)
        │  Car.createCar → getCarManager(PROPERTY_SERVICE)
        ▼
① android.car.hardware.property.CarPropertyManager   (AOSP 新路径)
        │  Binder
        ▼
   CarService → VHAL → 车端 ECU

2. ① LocalCarManager —— Car 实例单例

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

  • 持有双重检查锁的 mCar 单例(L18, L82-94)
  • Car.createCar(ctx, null, CAR_WAIT_TIMEOUT_WAIT_FOREVER, mLifecycleListener)(L87-88)——永远等 CarService 上线,不轮询
  • import 实证(核验):import android.car.Car;(AOSP)
  • 主 listener(L44-64):每次 ready/died 都遍历 mListeners 链表通知业务方

对外 API:

方法行号说明
createCar(Context, CarServiceLifecycleListener)L82创建 Car 并挂 listener
getCar()L124ready 才返回 Car,否则 null
unregisterListener(...)L108解注册

关于 ANR:项目用 WAIT_FOREVER 但在独立线程执行,不 block 主线程。业务侧禁止自己 new Car,必须走 LocalCarManager


3. ② NewBaseCarServiceManager —— Car 服务获取模板

路径:base/settingsVehicleLib/.../miauto/vehicle/NewBaseCarServiceManager.java

所有 Car.*Manager 的抽象基类(模板方法)。

  • 构造时即调 LocalCarManager.getInstance().createCar(...)(L23),把 lifecycle listener 加进链表
  • 抽象方法 getServiceName()(L97)——子类返回服务名(如 Car.PROPERTY_SERVICE
  • onLifecycleChanged(L36-64):ready 时 car.getCarManager(getServiceName()) 拿目标 Manager;disconnect 时置 null
  • 对外:registerCarServiceListener(OnCarServiceListener)(L71)、isConnected()(L93)、disconnect()(L86)

约束:要用 CarHudControlManager / CarPowerManager / CarUserPositionManager 等其它 Car 服务,都应继承 NewBaseCarServiceManager 复用 LocalCarManager不要另起 Car.createCar


4. ③ VehicleControlManager —— AOSP CarPropertyManager 直接封装

路径:base/settingsVehicleLib/.../miauto/vehicle/VehicleControlManager.java

⚠️ 类头注释明确”不公开,统一使用 SettingsCarPropertyManager”。业务禁止直接用。

  • extends NewBaseCarServiceManager<CarPropertyManager>,泛型参数是 AOSP 的 android.car.hardware.property.CarPropertyManager(核验 import 实证)
  • getServiceName()(L52-54)返回 Car.PROPERTY_SERVICE
  • 所有 register/unregister 委托给单例 CarPropertyCacheManager(L62-63, L72-73)——关键去重层
  • 所有 set 方法丢到 ThreadPoolUtils.ASYNC.execute(...) 异步执行(L115, L130, L145, L172);同步版本是 xxxBySync 后缀

关键方法(import 实证:import android.car.hardware.property.CarPropertyManager; import mi.car.config.CommonConstants;):

方法行号说明
getServiceName()L52-54返回 Car.PROPERTY_SERVICE
registerPropertyCallback(cb, propId)L56委托给 CarPropertyCacheManager
unregisterPropertyCallback(cb, propId)L66委托给 CarPropertyCacheManager
getIntProperty / setIntProperty直接走 mCarServiceManager.getProperty/setProperty
setIntPropertyBySyncL118同步版本
getProperty(propId, areaId)L161<T> CarPropertyValue<T>

5. ④ CarPropertyCacheManager —— 全局去重缓存分发器(单例)

路径:base/settingsVehicleLib/.../miauto/vehicle/CarPropertyCacheManager.java

这是项目最有价值的封装之一:解决”多个 Controller 监听同一 propId 时,不要重复向 server 注册”的问题。

  • 单例(L48-54)
  • ConcurrentHashMap<Integer, CopyOnWriteArraySet<CarPropertyEventCallback>> mCallBackMap(L36-37)——propId → 多 callback

内部 callback mInternalCarPropertyEventCallback(L56-149):

// L61  车端上报到达
public void onChangeEvent(CarPropertyValue carPropertyValue) {
    mPropertiesCache.put(propId#areaId, value);   // 全局缓存
    // 遍历 mCallBackMap[propId] 分发给所有订阅者
}
 
// L132  错误事件
public void onErrorEvent(...) { /* 维护 mErrorEvents map 并转发 */ }

registerPropertyCallback(CarPropertyManager, callback, propertyId)(L159-194)——核心去重逻辑

  • 若该 propId 已被任何 callback 注册过:只往本地 map 加 callback,不再向 server register(L163-185)——多 Controller 共享一次 Binder 注册
  • 首次注册才调 carPropertyManager.registerCallback(mInternalCarPropertyEventCallback, propId, SENSOR_RATE_ONCHANGE)(L192-193)
  • 注册时若已有缓存或错误事件,立即回放给新 callback(callbackByCache L197-228)

这层的意义:CarService 的 Binder 注册是昂贵的,且同 propId 多次注册会冲突。CarPropertyCacheManager 让 N 个 Controller 监听同一 propId 时,只向 server 注册 1 次。


6. ⑤ SettingsCarPropertyManager —— 业务级 Manager(最常用)

路径:base/settingsVehicleLib/.../miauto/vehicle/SettingsCarPropertyManager.kt

业务侧统一入口。per-Controller 实例(不是单例)。

6.1 构造

构造行号说明
constructor(context)L65@Deprecated
constructor(context, lifecycle)L69推荐——自动绑定生命周期,onDestroy 自动 release()

内部组件:

  • mCarPropMgr: VehicleControlManager(L96-98)——实际下层
  • mEventCallbackDispatcher: SettingsCarPropertyEventCallback(L145-147)——唯一对接 CarPropertyCacheManager 的 callback
  • mPropertiesCache: HashMap<String, CarPropertyValue<*>>(L95,per-instance 缓存,key=propId#areaId
  • mTimeoutHandler(L149-153)——set 后启动超时监控
  • mObservedPropertyIdSet / mNoNeedObservedPropertyIdSet(L109, L113)——区分主动监听 vs 被动监听

6.2 读 API(行号核验准确)

方法行号说明
getIntProperty(propId, areaId, defaultValue)L331读 int,带缓存
getIntPropertyWithoutCache(...)L340强制走远程
getFloatProperty / getBooleanProperty / getIntArrayProperty / getStringProperty / getByteArrayPropertyL349-386各类型读
getProperty<T>(propId, areaId) / getPropertyWithoutCacheL349-386泛型读

典型业务写法(AtmosphereLightBrightnessProgressPrefController.java:82):

int currentMode = mCarPropertyManager.getIntProperty(
        MiCarPropertyIds.LightCtrl.MOOD_LIGHT_MODE_SETTINGS);

6.3 写 API(行号核验准确)

方法行号说明
setCarProperty(propId, propVal, areaId, isSync)L423主推,带超时监控 + 类型校验,按 Int/Float/IntArray/List 分发
setCarPropertyBySync(propId, propVal, areaId)L408同步版本
setCarProperty(data: VehicleData)L416用 VehicleData 包装
setCarPropertyWithoutDelayCheck(...)L482不启动超时监控、不做类型校验(只读状态查询用)

典型业务写法(EnergyCableUnlockActivity.kt:41):

mCarPropertyManager.setCarProperty(CABLE_UNLOCK, Body.DCDAcUnlockSet.UNLOCK)

Controller 子类下发(推荐,基类已封装类型分发):

setVehicleProperty(MiCarPropertyIds.LightCtrl.FOG, newVal)   // CarPropertyMgrPreferenceController.java L620

6.4 回调注册

方法行号说明
setCarManagerCallback(CarPropertyManagerCallback)L306注册业务回调 + 同时挂 OnCarServiceListener
registerPropertyCallback(ArraySet<Int>)L237显式注册关心的 propId 集合
unregisterPropertyCallback(areaId)L265注销
retryRegisterPropertyCallbackL284重试注册

⚠️ 关键易错点registerPropertyCallback 必须在 onPropertyManagerPrepared 之后调,否则服务没连上注册失败。基类 CarPropertyMgrPreferenceController 已处理。

6.5 超时监控(核心机制)

下发信号后,如果车端长时间不回弹上报,UI 会卡在”已下发”状态。超时监控解决这个问题。

方法行号作用
startTimeOutMonitor / startTimeOutMonitorRealL530-562set 后启动;用 VehiclePropertyConfig.getCustomTimeoutValue 取阈值,mTimeoutHandler.sendMessageDelayed
processTimeoutL155-189根据 CarPropertyTimeoutFlag 决定 CONTINUE/RESTART/REMOVE
handlePropertyTimeoutL191超时触发:mCallback.onSetPropertyTimeout + 数据校验 onDataVerifyFailed

CarPropertyTimeoutFlagCarPropertyTimeoutFlag.java)三态:

  • REMOVE:移除超时,回滚 UI
  • CONTINUE:继续等
  • RESTART:重新计时(中间态信号,如车窗升降中)

这就是项目里所有”按钮按了但没生效会自动回弹”的实现根源:超时 → onSetPropertyTimeoutrestorePropertyToPrevious03 篇)。

6.6 事件分发(内部类)

SettingsCarPropertyEventCallback implements CarPropertyEventCallback(L658-692):

// L662  上行信号到达
override fun onChangeEvent(value: CarPropertyValue<*>) {
    // mock 错误/超时检测
    cachePropVal                                    // per-instance 缓存
    processTimeout                                  // 终止/重启超时
    if (propId 命中 mObservedPropertyIdSet) {
        mCallback.onPropertyChanged(value)          // 仅 observedSet 内的才回调业务
    }
}
 
// L687  错误
override fun onErrorEvent(propId, areaId) {
    // 移除超时 msg
    mCallback.onSetPropertyError(propId, areaId)
}

⚠️ 常见坑:如果业务忘了把 propId 加入 registerPropertyCallback 的 observedSet,即使底层有事件,onPropertyChanged不会回调,只打 w 级警告 "un-excepted observe propId"

6.7 隐式订阅(容易踩坑)

remoteGetProperty(L391-405)拿到值后,若 propId 不在 mObservedPropertyIdSet,会自动注册底层 callback 并加入 mNoNeedObservedPropertyIdSet——意思是”get 过一次,后续变化就会被通知”。


7. 全局入口:GlobalCarPropertyManager

路径:base/settingsVehicleLib/.../miauto/GlobalCarPropertyManager.kt(object 单例,L16)

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

SettingsCarPropertyManager 的区别:后者 per-Controller(绑页面生命周期);GlobalCarPropertyManager全局,给页面外逻辑用。


8. 关键设计要点总结

  1. 业务禁止绕过 SettingsCarPropertyManager:一切缓存、超时、生命周期管理都在它做。VehicleControlManager 不公开。
  2. 去重靠 CarPropertyCacheManager:同 propId 多 Controller 只向 server 注册 1 次。
  3. 超时靠 mTimeoutHandler:默认按 VehiclePropertyConfig 配置的阈值,超时自动回滚。
  4. 缓存两层CarPropertyCacheManager.mPropertiesCache(进程级)+ SettingsCarPropertyManager.mPropertiesCache(per-Controller 级)。
  5. 生命周期自动SettingsCarPropertyManager(context, lifecycle) 自动绑页面生命周期,onDestroy 自动 release(),业务通常无需手动解注册。
  6. 异步默认setCarProperty 默认 isSync=false(异步,丢线程池);xxxBySync 才同步。

9. 关键路径速查

路径
① Car 单例base/settingsVehicleLib/.../vehicle/LocalCarManager.java
② 服务模板base/settingsVehicleLib/.../vehicle/NewBaseCarServiceManager.java
③ AOSP 封装base/settingsVehicleLib/.../vehicle/VehicleControlManager.java
④ 去重缓存base/settingsVehicleLib/.../vehicle/CarPropertyCacheManager.java
⑤ 业务 Managerbase/settingsVehicleLib/.../vehicle/SettingsCarPropertyManager.kt
全局 Managerbase/settingsVehicleLib/.../miauto/GlobalCarPropertyManager.kt
回调接口base/settingsVehicleLib/.../vehicle/CarPropertyManagerCallback.java
超时枚举base/settingsVehicleLib/.../vehicle/CarPropertyTimeoutFlag.java
数据类base/settingsVehicleLib/.../vehicle/VehicleData.kt