04 · 超时监控与回调机制
源码:
SettingsCarPropertyManager.kt行 95-201, 306-319, 530-609, 658-692
SCPM 区别于「直接调 AOSP」的三个核心增值机制:超时监控、回调双层分发、缓存策略。
1. 超时监控状态机
下发信号后,车端应在一定时间内回弹确认。SCPM 用一个主线程 Handler 实现完整的等待→回弹/超时流转。
1.1 核心代码位置
| 函数 | 行号 | 作用 |
|---|---|---|
startTimeOutMonitor | 530-539 | 入口,BATCH_PROP 遍历集合 |
startTimeOutMonitorReal | 541-562 | 真正启动计时 |
processTimeout | 155-189 | 车端回弹时的决策 |
handlePropertyTimeout | 191-201 | 真超时处理 |
getTimeoutMsgId | 568-570 | msgId = (propId#areaId).hashCode() |
isInHMITriggerWaitingDuration | 564-566 | 查是否还在等待(hasMessages) |
1.2 状态流转图
stateDiagram-v2 [*] --> 下发信号: setCarProperty 下发信号 --> 启动判断: startTimeOutMonitorReal state 启动判断 <<choice>> 启动判断 --> 不计时: propId 不在 observedSet 启动判断 --> 不计时: timeoutValue == TIMEOUT_IGNORE 启动判断 --> 等待回弹: 满足条件 sendMessageDelayed 等待回弹 --> 等待回弹: 车端回弹 CONTINUE 等待回弹 --> 等待回弹: 车端回弹 RESTART 重启计时 等待回弹 --> [*]: 车端回弹 REMOVE(默认) 取消计时 等待回弹 --> 真超时: 延时到期 真超时 --> onSetPropertyTimeout: 回调业务层 真超时 --> 值比对: 下发值 vs 当前缓存值 值比对 --> [*]: 一致 值比对 --> onDataVerifyFailed: 不一致(数据校验失败) 不计时 --> [*]
1.3 关键规则
- 只对关心的信号计时:
startTimeOutMonitorReal(546) 检查mObservedPropertyIdSet.contains(timeoutPropId),没注册的信号不计时——因为超时也不会回弹,计时无意义。 - 下行/上行信号映射:
VehiclePropertyConfig.getMappingTimeoutPropId(propId)(542) 处理「下发 A 信号,回弹看 B 信号」的场景。 - timeoutValue 可按值定制:同一个 propId 不同 value 可以有不同超时(如开到不同高度耗时不同),
getCustomTimeoutValue(propId, value)。 - 三态 Flag:业务层通过
getTimeoutFlag(value)(674 调用) 决定回弹后的行为——CONTINUE(继续等,回弹的不是终态)、RESTART(重新计时)、默认REMOVE(取消计时)。
2. 回调双层结构 + 事件分发
2.1 双层结构
graph TD subgraph 下层["下层:唯一底层 dispatcher"] A["SettingsCarPropertyEventCallback<br/>(内部类, implements AOSP CarPropertyEventCallback)"] A1["onChangeEvent :662"] A2["onErrorEvent :687"] end subgraph 内部["内部处理链"] B1["mockErrorEvent 拦截 :664"] B2["mockTimeoutEvent 拦截 :669"] B3["cachePropVal 缓存 :673"] B4["processTimeout 超时决策 :674"] B5{"propId in observedSet?"} end subgraph 上层["上层:业务层回调"] C1["onPropertyChanged :680"] C2["onSetPropertyError :690"] C3["onSetPropertyTimeout :194"] C4["onDataVerifyFailed :199"] C5["onPropertyManagerPrepared :311"] C6["onPropertyManagerReleased :315"] end CarEvent["车端 CarPropertyEvent"] --> A A --> A1 A --> A2 A1 --> B1 B1 -->|mock error| A2 B1 -->|正常| B2 B2 -->|mock timeout| Drop["直接返回"] B2 -->|正常| B3 B3 --> B4 B4 --> B5 B5 -->|是| C1 B5 -->|否| Warn["打 w 日志 :682"] A2 --> C2 B4 -.->|真超时| C3 B4 -.->|下发值!=缓存值| C4 CarService["registerCarServiceListener :308"] --> C5 CarService --> C6
2.2 onChangeEvent 处理顺序(662-684)
1. mockErrorEvent? → 是:转 onErrorEvent,return
2. mockTimeoutEvent? → 是:直接 return(吞掉)
3. cachePropVal → 更新缓存
4. processTimeout → 按 CarPropertyTimeoutFlag 处理计时
5. observedSet 过滤 → 在集合内:onPropertyChanged 上抛
→ 不在:打 w 日志("un-excepted observe propId")
⚠️ 第 5 步是常见坑:如果业务层忘了
registerPropertyCallback(ids)把 propId 加进 observedSet,即使底层有事件,onPropertyChanged也不会被回调,只在日志里看到 w 级警告。这是「为什么我 set 了也 get 了,但收不到变化通知」的常见原因之一。
2.3 连接监听(306-319)
setCarManagerCallback 在注册业务回调的同时,向 VehicleControlManager 注册了 OnCarServiceListener:
onCarServiceManagerPrepared→mCallback.onPropertyManagerPrepared()(业务层通常在此registerPropertyCallback+ 刷新 UI)onCarServiceManagerReleased→mCallback.onPropertyManagerReleased()+onServiceDisconnect()(清缓存、清 handler 消息)
3. 缓存策略
3.1 核心位置
| 组件 | 行号 | 作用 |
|---|---|---|
mPropertiesCache | 95 | HashMap<String, CarPropertyValue<*>> |
getCacheKey | 576-578 | "propId#areaId" |
getCachedProp | 580-584 | 读 |
cachePropVal | 586-609 | 写 |
remoteGetProperty | 391-405 | 远端获取 + 缓存 + 隐式注册 |
getProperty | 371-378 | 优先缓存 |
getPropertyWithoutCache | 381-386 | 强制远端 |
3.2 写缓存决策(cachePropVal 586-609)
flowchart TD In["输入 CarPropertyValue"] --> Key["key = propId#areaId"] Key --> First{"首次缓存?<br/>cache[key]==null"} First -->|是| FirstVal["getPropValueForFirstCache(value)"] FirstVal --> HasInit{"有初始终态值?"} HasInit -->|是| StoreInit["缓存初始终态值<br/>直接 return"] HasInit -->|否| Ignore{"业务层 isIgnoreCache?"} First -->|否| Ignore Ignore -->|true| Skip["跳过,不缓存"] Ignore -->|false| Store["cache[key] = value"]
3.3 读缓存决策(getProperty 371-378)
flowchart TD Get["getProperty propId, areaId"] --> TryCache["getCachedProp"] TryCache --> Hit{"缓存命中?"} Hit -->|是| Return["返回缓存值"] Hit -->|否| Remote["remoteGetProperty"] Remote --> Conn{"isConnected?"} Conn -->|否| Null["返回 null"] Conn -->|是| Fetch["VCM.getProperty → AOSP"] Fetch --> DoCache["cachePropVal 缓存"] DoCache --> AutoReg{"propId 在 observedSet?"} AutoReg -->|否| Reg["自动注册 callback<br/>加入 NoNeedObservedSet"] AutoReg -->|是| Done Reg --> Done["返回值"]
3.4 三个关键设计
- key =
propId#areaId:同一属性的不同区域(如左前门/右后门)各自独立缓存,互不污染。 - 首次缓存注入终态值:
getPropValueForFirstCache(590-597) 让 UI 首次展示时拿到的是「终态值」而非中间值(避免 UI 抖动)。这对 JIRA 1839 的车门闪烁场景至关重要——但前提是首次缓存前有调过getProperty,而DoorUnLockActivity恰恰没预读,所以没吃到这个兜底。 - 业务层可控:
isIgnoreCache(value)(605) 让业务层决定特定值是否进缓存(如过渡态值不缓存)。
附:三个机制的协作关系
graph LR Write["写 setCarProperty"] --> Timeout["启动超时计时"] Timeout --> Wait["等待回弹"] Car["车端回弹"] --> Dispatcher["底层 dispatcher onChangeEvent"] Dispatcher --> Cache["更新缓存"] Dispatcher --> TimeoutProc["处理超时 Flag"] TimeoutProc -->|REMOVE| Cancel["取消计时"] TimeoutProc -->|CONTINUE| Wait TimeoutProc -->|RESTART| Timeout Dispatcher --> Notify["onPropertyChanged 上抛"] Wait -->|超时| Fail["onSetPropertyTimeout + onDataVerifyFailed"] Read["读 getProperty"] -.->|优先| Cache Cache -.->|未命中| Remote["remoteGetProperty 隐式注册"] Remote -.-> Dispatcher
← 返回 00-README.md | 业务实战见 座舱车门知识库