05 · MANNPROB-1839 根因:车门闪烁全开
JIRA: MANNPROB-1839 车型:Lemans 27B(newton)/ FC7300 | 版本:26.06.27.X.CN.D | 复现:Always 前提:仅右后车门打开(
DoorRiReSts=0x1)| 现场:2026-06-29 10:47:39
现象
首次 LockFltRemdForAlive=0x1(仅右后门开)触发离车提醒弹窗,弹窗车门图标全开(闪烁),几秒后才回到正确的「仅右后门开」。
根因(confidence: high)
「首帧 XML 默认态错误 + onCreate 不预读 Door.STATE」双结构 bug:
lock_door_unlock_layout.xml:55/64/73/82/91— 5 个车门 ImageView 默认android:src全部是*_model_unlock(开态)。DoorUnLockActivity.java:57-83onCreate— 仅setContentView+findViewById+new SettingsCarPropertyManager,没有任何Door.STATE同步预读。DoorUnLockActivity.java:86-89onPropertyManagerPrepared— 等 Car 服务连接后才registerPropertyCallback,异步。CarPropertyCacheManager.java:186-194— 首次注册路径(else 分支)显式不调用同步callbackByCache,6 次onChangeEvent必须经 binder 异步回放。
→ 首帧(bugreport reportDrawFinished @10:47:39.116)按 XML 默认渲染 = 5 门全开;首批 onChangeEvent(@10:47:39.117–.176,60ms 窗口)逐门到达后才 setImageResource 改回 → 60ms 内多帧切换 = 用户感知的「车门全开闪烁」。
首帧竞争时序
sequenceDiagram autonumber participant Act as DoorUnLockActivity participant Render as 渲染管线 participant SCPM as SettingsCarPropertyManager participant CPCM as CarPropertyCacheManager participant AOSP as AOSP CarPropertyManager Note over Act: onCreate [:57-83] Act->>Act: setContentView(layout)<br/>⚠️ 5 iv 默认 *_model_unlock Act->>SCPM: new + setCarManagerCallback(this) Act->>Render: reportDrawFinished 首帧提交<br/>(10:47:39.116)<br/>🎨 渲染 = XML 默认 = 5门全开 Act->>Act: onPropertyManagerPrepared [:86-89] Act->>SCPM: registerPropertyCallback(Door.STATE) [:88] SCPM->>CPCM: 首次注册(else分支) Note over CPCM: ⚠️ 不调 callbackByCache<br/>必经 binder 异步 CPCM->>AOSP: registerCallback Note over AOSP: 车端 Door.STATE 6 area 缓存回放 par 60ms 窗口逐门到达 AOSP-->>CPCM: a=ROW_1_LEFT v=0 (.117) CPCM-->>Act: onPropertyChanged [:107] → iv_drv 关 and AOSP-->>CPCM: a=ROW_1_RIGHT v=0 (.138) CPCM-->>Act: → iv_pass 关 and AOSP-->>CPCM: a=ROW_2_LEFT v=0 (.158) CPCM-->>Act: → iv_rear_left 关 and AOSP-->>CPCM: a=ROW_2_RIGHT v=1 (.176) CPCM-->>Act: → iv_rear_right 保持开(右后真开) and AOSP-->>CPCM: a=HOOD v=0 / a=REAR v=0 CPCM-->>Act: → iv_tail gone end Note over Render: 60ms 内 3-4 个 vsync 多帧切换<br/>= 用户感知的「闪烁」
证据链(三层 ground truth)
代码证据(全部已验证存在)
| 位置 | 内容 | 验证状态 |
|---|---|---|
lock_door_unlock_layout.xml:55 | android:src="@drawable/ic_drv_model_unlock" | ✅ 已 grep 确认 |
lock_door_unlock_layout.xml:64 | ic_pass_model_unlock | ✅ |
lock_door_unlock_layout.xml:73 | ic_rear_left_model_unlock | ✅ |
lock_door_unlock_layout.xml:82 | ic_rear_right_model_unlock | ✅ |
lock_door_unlock_layout.xml:91 | ic_tail_model_unlock | ✅ |
DoorUnLockActivity.java:57-83 | onCreate 无 Door.STATE 预读 | ✅ 已读确认 |
DoorUnLockActivity.java:86-89 | onPropertyManagerPrepared 异步注册 | ✅ 已读确认 |
DoorUnLockActivity.java:98-147 | onPropertyChanged 按 area+value setImageResource | ✅ 已读确认 |
CarPropertyCacheManager.java:186-194 | 首注册 else 分支不调 callbackByCache | 依赖 Agent 分析 |
bugreport 时序铁证
| 时间 | 事件 | 主txt 行号 |
|---|---|---|
| 10:47:39.116 | reportDrawFinished 首帧提交(XML 默认=5门全开) | L212420 |
| 10:47:39.117–.176 | onChangeEvent 逐门回调(改回正确态) | L212425–212570 |
| 10:47:39.179 | 自打日志 [Delay] register callback 138ms | L212591 |
数据正确性(App 收到的值完全正确)
.117 0x61403156 v=1 ← 离车提醒触发
.117 0x66403101 a=REAR v=0
.117 0x66403101 a=ROW_1_LEFT v=0
.138 0x66403101 a=ROW_1_RIGHT v=0
.158 0x66403101 a=ROW_2_LEFT v=0
.176 0x66403101 a=ROW_2_RIGHT v=1 ← 唯一打开的门(右后)
.176 0x66403101 a=HOOD v=0
→ App 收到的值与车端完全一致:仅右后门开,数据层正确无误。bug 纯在渲染层。
责任分层
| 层 | 责任 | 证据 |
|---|---|---|
| 车端 MCU/CAN | 无责 | milogcat 10:47:39 前后零发布;右后门 a=64 v=1 与现场一致 |
| CarPropertyService 中间件 | 无责 | 6 area 缓存值全部正确,仅送达时间分散在 60ms 窗口 |
| App 渲染层(DoorUnLockActivity + XML) | 根因 | XML 默认全开 + onCreate 不预读 + 首注册异步 |
解决方案(三重防御)
⚠️ 安全警告:仅把 XML 默认改为「关」不可单独发布! 若回调注册永久失败(car_service 异常),车门会显示全关,而本场景右后门实际开着 → 给车主「门都关好了」的虚假安全感,比全开更危险。XML 改关必须配合同步预读。
| 层级 | 方案 | 改动点 |
|---|---|---|
| 主修(primary) | 首帧 Door.STATE 数据就绪后再渲染车门图标区 | DoorUnLockActivity.java:86-89(注册逻辑)+ 布局车门图标区初始 visibility=invisible,数据就绪后 visible |
| 表层兜底(secondary) | XML 默认 src 改关态 + iv_tail 默认 gone | lock_door_unlock_layout.xml:55/64/73/82 → ic_*_model;:91 加 visibility=gone |
| 纵深防御(tertiary) | onPropertyManagerPrepared 中 getProperty(Door.STATE, area) 同步预读各 area 当前值 | DoorUnLockActivity.java:86-89 加同步预读 |
验证方法
- mock 复现(车机 adb root):
dumpsys android.hardware.automotive.vehicle.IVehicle/micar --mock_from_car 0x61403156 -a 0 -i 1触发弹窗,慢动作录屏看首帧是否还全开(修复后应为「不可见→直接正确态」)。 - 回归:前提仅右后开时触发,确认首帧不再闪烁;断 car_service 模拟注册失败,确认显示真实态而非全关门。
mock 复现结论
newton 实机 AIDL mock 能稳定触发 DoorUnLockActivity,但「4 门全开首帧」不稳定:
- once 模式偶发复现(车机刚连接、缓存全空的初始冷态):录屏逐帧见 8.906s 4 门开 → 9.144s 全关(238ms 开态窗口)。
- 后续 mock 不复现:
car_service系统层缓存 Door.STATE 全关,onChangeEvent 从 car_service 快速到达,快于首帧 alpha 渐显 → 直接设关,跳过 XML 默认开态窗口。force-stop car.settings 只清 app 层缓存,car_service 层缓存仍在。要稳定复现需 reboot 车机让 car_service 冷。
→ mock 定位为「触发链 + 信号时序对齐」验证工具;bug 的确定性证据是现场录屏 + bugreport 时序 + 源码,不依赖 mock。
字段矛盾
- 「是否误报=误报」 vs 「故障原因=代码bug + O77S已修复」:以后者为准,现象真实必现,「误报」是分类错误。
- 评论 a=64 标「左后」 vs AOSP 标准 a=64=ROW_2_RIGHT=右后:评论作者标反了 area 位掩码,与前提「仅右后开」吻合。
与 1838 的关系
1838 和 1839 都是 DoorUnLockActivity 首帧初始化问题:
- 1838:
setAlpha渐入期间内容未 ready(弹多次) - 1839:XML 默认态错误(车门闪烁)
- 统一治理模式:「弹窗首帧数据未就绪前不渲染内容」。