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:

  1. lock_door_unlock_layout.xml:55/64/73/82/91 — 5 个车门 ImageView 默认 android:src 全部是 *_model_unlock(开态)
  2. DoorUnLockActivity.java:57-83 onCreate — 仅 setContentView + findViewById + new SettingsCarPropertyManager没有任何 Door.STATE 同步预读
  3. DoorUnLockActivity.java:86-89 onPropertyManagerPrepared — 等 Car 服务连接后才 registerPropertyCallback异步
  4. 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:55android:src="@drawable/ic_drv_model_unlock"✅ 已 grep 确认
lock_door_unlock_layout.xml:64ic_pass_model_unlock
lock_door_unlock_layout.xml:73ic_rear_left_model_unlock
lock_door_unlock_layout.xml:82ic_rear_right_model_unlock
lock_door_unlock_layout.xml:91ic_tail_model_unlock
DoorUnLockActivity.java:57-83onCreate 无 Door.STATE 预读✅ 已读确认
DoorUnLockActivity.java:86-89onPropertyManagerPrepared 异步注册✅ 已读确认
DoorUnLockActivity.java:98-147onPropertyChanged 按 area+value setImageResource✅ 已读确认
CarPropertyCacheManager.java:186-194首注册 else 分支不调 callbackByCache依赖 Agent 分析

bugreport 时序铁证

时间事件主txt 行号
10:47:39.116reportDrawFinished 首帧提交(XML 默认=5门全开)L212420
10:47:39.117–.176onChangeEvent 逐门回调(改回正确态)L212425–212570
10:47:39.179自打日志 [Delay] register callback 138msL212591

数据正确性(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 默认 gonelock_door_unlock_layout.xml:55/64/73/82ic_*_model:91visibility=gone
纵深防御(tertiary)onPropertyManagerPreparedgetProperty(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 默认态错误(车门闪烁)
  • 统一治理模式:「弹窗首帧数据未就绪前不渲染内容」

上一篇:04-MANNPROB-1838-根因-弹窗弹两次.md | 下一步:06-调试与mock复现指南.md