02 - 应用层主通道:CarProperty 机制
本篇是 App 内信号传递的核心。讲
settingsVehicleLib如何在 AOSPCarPropertyManager之上封装出缓存去重 / 超时监控 / 生命周期托管 / 回调分发收敛四件套,让业务侧只需调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() | L124 | ready 才返回 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 |
setIntPropertyBySync | L118 | 同步版本 |
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(
callbackByCacheL197-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 的 callbackmPropertiesCache: 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 / getByteArrayProperty | L349-386 | 各类型读 |
getProperty<T>(propId, areaId) / getPropertyWithoutCache | L349-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 L6206.4 回调注册
| 方法 | 行号 | 说明 |
|---|---|---|
setCarManagerCallback(CarPropertyManagerCallback) | L306 | 注册业务回调 + 同时挂 OnCarServiceListener |
registerPropertyCallback(ArraySet<Int>) | L237 | 显式注册关心的 propId 集合 |
unregisterPropertyCallback(areaId) | L265 | 注销 |
retryRegisterPropertyCallback | L284 | 重试注册 |
⚠️ 关键易错点:
registerPropertyCallback必须在onPropertyManagerPrepared之后调,否则服务没连上注册失败。基类CarPropertyMgrPreferenceController已处理。
6.5 超时监控(核心机制)
下发信号后,如果车端长时间不回弹上报,UI 会卡在”已下发”状态。超时监控解决这个问题。
| 方法 | 行号 | 作用 |
|---|---|---|
startTimeOutMonitor / startTimeOutMonitorReal | L530-562 | set 后启动;用 VehiclePropertyConfig.getCustomTimeoutValue 取阈值,mTimeoutHandler.sendMessageDelayed |
processTimeout | L155-189 | 根据 CarPropertyTimeoutFlag 决定 CONTINUE/RESTART/REMOVE |
handlePropertyTimeout | L191 | 超时触发:mCallback.onSetPropertyTimeout + 数据校验 onDataVerifyFailed |
CarPropertyTimeoutFlag(CarPropertyTimeoutFlag.java)三态:
REMOVE:移除超时,回滚 UICONTINUE:继续等RESTART:重新计时(中间态信号,如车窗升降中)
这就是项目里所有”按钮按了但没生效会自动回弹”的实现根源:超时 →
onSetPropertyTimeout→restorePropertyToPrevious(03 篇)。
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-30init) getCarManager()(L32):CarConfigManager/MenuAccountController等非页面逻辑通过它直接读信号addPropertyCallback(ids, callback)/removePropertyCallback(L40-59):全局多 callback 监听同一信号集
与
SettingsCarPropertyManager的区别:后者 per-Controller(绑页面生命周期);GlobalCarPropertyManager是全局,给页面外逻辑用。
8. 关键设计要点总结
- 业务禁止绕过
SettingsCarPropertyManager:一切缓存、超时、生命周期管理都在它做。VehicleControlManager不公开。 - 去重靠
CarPropertyCacheManager:同 propId 多 Controller 只向 server 注册 1 次。 - 超时靠
mTimeoutHandler:默认按VehiclePropertyConfig配置的阈值,超时自动回滚。 - 缓存两层:
CarPropertyCacheManager.mPropertiesCache(进程级)+SettingsCarPropertyManager.mPropertiesCache(per-Controller 级)。 - 生命周期自动:
SettingsCarPropertyManager(context, lifecycle)自动绑页面生命周期,onDestroy自动release(),业务通常无需手动解注册。 - 异步默认:
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 |
| ⑤ 业务 Manager | base/settingsVehicleLib/.../vehicle/SettingsCarPropertyManager.kt |
| 全局 Manager | base/settingsVehicleLib/.../miauto/GlobalCarPropertyManager.kt |
| 回调接口 | base/settingsVehicleLib/.../vehicle/CarPropertyManagerCallback.java |
| 超时枚举 | base/settingsVehicleLib/.../vehicle/CarPropertyTimeoutFlag.java |
| 数据类 | base/settingsVehicleLib/.../vehicle/VehicleData.kt |