68 · 实战案例走读 — 用真实 JIRA 学 CachedBluetoothDevice

源码基线:/home/zbc/car/MiCarSettings(f3dif = A17 主线) ⚠️ 行号时效:本章行号原样摘自各案例 root-cause.md 落笔时点(2026-07-16 ~ 08-17,不同案例来自不同分支/副本快照)。主仓代码已演进的引用点:1922 的 Controller 行号漂移 +15~16、3492 的 BEM:514-524 现为 :364-387(逻辑逐行一致)、3187 的 :460-468 现为 :478-492(已是修复后代码)、MiBluetoothUtils getBtDrawable kt:302 现为 :328。同一调用点在不同案例中行号可能不同(如 1922 的 :316 与 134132 的 :331 是同一个拦截分支清表调用)。 案例素材:车机JIRA/jira-data/{KEY}/analysis/root-cause.md(对抗审查后定案的分析文档) 前置阅读:62-字段逐个精讲65-属性刷新与回调分发

0. 为什么要用案例学这个类

CachedBluetoothDevice 的字段和方法,单看代码只是一堆名词。它们的语义边界(什么时候可信、什么时候会骗你、谁和你抢)只有在真实故障里才显形。本篇把 8 条已定案的车机蓝牙 JIRA 按教学顺序重排——每条案例都精准打在这个类(或它的直接协作者)的一个知识点上。

一句话预告全篇:

#案例一句话根因打中的知识点
12403 图标退化属性是在线透传不是快照,adapter OFF 窗口重算拿 null属性读取语义(62 §2
23187 “已保存”闪烁广播型回调不按设备过滤,一台的状态污染另一台回调分发(65
31922 删后 15s 空窗clearNonBondedDevices() 连坐全清,合法设备被误逐缓存管理(63
43492 永久扫描不到DeviceFoundHandler 对 cached+BOND_NONE 两分支都不命中分发盲区(与 1922 同源变体)
5134132 蓝牙栏卡顿synchronized 临界区内做同步 binder IPC,持锁秒级并发与锁(62 §3
62960 双芯片已配对消失缓存的两条注入路径在双 adapter 下全部失效架构边界(63
73960 替换弹框误弹服务侧 endpoint 账务 ≠ 真实连接账务,两套计数背离数据源一致性
82846 配对弹窗闪现BONDED 广播被 SDP 结构性门控 ~350ms,dismiss 跑不赢配对链路与广播时序

阅读法:每条案例先看「现象」,自己想 30 秒「如果是你会怎么查」,再看「根因」。这是把知识变成直觉的最快路径。


1. MANNPROB-2403:关蓝牙时图标退化 —「属性是在线查询,不是快照」

现象:关蓝牙的 TURNING_OFF→STATE_OFF738ms 窗口内,已配对手机的”手机图标”闪退为通用蓝牙图标(2/5 偶现)。

新手直觉误区:以为 CachedBluetoothDevice 里的 “Cached” 意味着属性被持久缓存、随时可读。

真实机制getBondState() / getBtClass() / getAlias() 这些方法直接透传 framework 的在线 API,自己不存值。而 adapter 已进入 OFF 状态时,framework 在线 API 恒返回 BOND_NONE / null。末端 getBtDrawable 对 null btClass 走 else 分支出通用图标。

本质:在 adapter OFF 的错误时机,用在线 API 重算本应持久化的语义数据。

落点

  • 父类 BluetoothPreferenceController:137 — 对所有 state 无条件 refreshPreferenceState(),未对 OFF 短路
  • CachedBluetoothDevice.getBondState()/getBtClass() — 无本地缓存兜底,直接透传 framework
  • MiBluetoothUtils.getBtDrawable(kt:302) — null btClass 走 else 出通用图标

修复:CachedBluetoothDevice 新增本地快照字段——STATE_ON 时在线查询并仅缓存 BOND_BONDED 稳定值,非 ON 时返回关断前快照(dcddif+xcddif 同步)。Gerrit 354880。

教学点

  1. 这个类的 “Cached” 缓存的是对象身份和回调状态,bond 状态/设备类这类语义数据历史上是在线查询的——这个案例正是补上本地快照的动机。
  2. 「偶现」的解释可以很物理:738ms 可见窗口 × 人眼注意力 = 视觉捕获概率,不是代码竞态。排查偶现问题时先问”窗口有多宽”。

2. MANNPROB-3187:双设备”已保存”闪烁 —「广播回调必须按设备过滤」

现象:双设备已连接,关开蓝牙自动回连中,设备先显示”已保存”约 800ms 再变”正在连接”(必现);单设备不出现

根因(注意”多设备才出现”这个特征直指污染):

onProfileConnectionStateChanged(设备A的回调)
    └─ 遍历列表【所有】preference 调 setConnecting   ← 没用回调参数 cachedDevice 的地址过滤
         └─ 设备B(正在连接)被 A 的 CONNECTED(state=2) 错误清掉 mIsConnectingState
              └─ isBusy && mIsConnectingState 判据失败 → else 分支 → 显示"已保存"
                   └─ 直到 B 自己的下一次回调才纠正(~800ms 后)

落点

  • BluetoothBondedDevicesPrefController.java:460-468 — 无过滤循环(根因命门
  • :463 setConnecting(state==STATE_CONNECTING)
  • :466 refreshDeviceUi 全量重渲染(放大器)
  • MiCarBluetoothBondedDevicePreference.kt:93-109getDeviceSummary 三分支(:104 是”正在连接”判据 isBusy && mIsConnectingState,else”已保存”兜底在 :106)
  • NewBluetoothDevicePreference.java:64mIsConnectingState
  • CachedBluetoothDevice.java:1043-1054isBusy

修复::463 加设备地址过滤 + :466 收窄 + 动画判据对齐(362953 合入 origin/dev_a17_0610,合并提交 8d4be76f6;dev 分支 65aa6bfcf)。后续 364728 必须随版。

教学点

  1. 广播型回调必须用回调参数过滤目标——回调给你 cachedDevice 就是让你用的,多设备场景下”遍历全体”是最常见的污染模式。
  2. mIsConnectingState(Controller 维护的旁路状态位)与 isBusy(设备自身状态)来源解耦即竞态:两套账各记各的,必然有窗口不一致。
  3. 一个对照知识:AOSP 原生在”空闲”时返回 null(条目空白),MiCar 的 else 兜底把协议栈等待窗口可视化成了”已保存”——所以这个 bug 才”可见”。UI 兜底文案会放大下层时序问题。
  4. 边界辨析:3187 = 双设备互相污染(跨设备),3205 = 单设备 isBusy 语义缺陷(isBusy 含 DISCONNECTING,误驱动画与置灰)——两案相邻但机制不同。(另注:“300ms 防抖 < 真实断开 isBusy 被放行”是 3950(3205 修复残留单)的机制,防抖本身是 362953 修复引入的。)

3. MANNPROB-1922:删配对后 15 秒空窗 —「连坐全清」

现象:删除已连接蓝牙设备后,列表 ~15s 只剩刚删设备、无新设备、无搜索动画(必现)。

根因BOND_NONE 回调触发两次冗余的 clearNonBondedDevices() 全量清空

  • 第一处:BluetoothUnbondedDevicesPrefController.java:295 — BOND_NONE 无条件 refreshUi(首要逐出点)→ innerUpdateState:162/:166 全清
  • 第二处::306/:316 拦截分支又 clear 一次(冗余)
  • CachedBluetoothDeviceManager.java:250-260clearNonBondedDevices 确为连坐全清:遍历所有缓存设备,凡 getBondState()!=BOND_BONDED 一律逐出

后果链:其它合法(但此刻未连接)设备被连坐清出缓存 → 必须等下一 discovery 周期(~12.8s)重新广播才能回来。“防秒回”的 lastUnboundDevice 只拦一次,被删设备 51ms 秒回,其它设备反而遭殃。

取证亮点canScan=false 在 bugreport 中零命中——扫描实际全程没断,“没搜索”是用户感知误判。用”应然日志的零命中”证明”怀疑的机制没发生”,是排障的基本功。

修复状态:未修。修法 = :295:316 两处必须同改:BOND_NONE 改局部 removePreference + 拦截分支删 clearNonBondedDevices 调用。(历史上 commit c4f59dd78 为 dangling 未合入。)

教学点

  1. clearNonBondedDevices()连坐全清 API——CachedBluetoothDeviceManager 没有”只移除一个设备”的公开方法,这决定了删配对路径的修复形态。
  2. BOND_NONE 回调路径的列表刷新粒度选择:局部移除 vs 全清重建,选错就是 15s 空窗。
  3. 一次性 flag(lastUnboundDevice)防秒回机制,及其”只拦一次”的失效形态。

4. MANNPROB-3492:详情页删配对后永久扫描不到 —「分发盲区」(1922 的同源变体)

现象:从设备详情页删除配对后,被删设备在主蓝牙页”可用列表”永久不出现,退出重进页面才恢复(必现)。对比 1922:那个是”整列表空 15s”,这个是”单个设备永久隐身”。

根因:三因协同——

  • (a) 详情页删除时,主蓝牙页 Controller 不活跃,错过 BOND_NONE 广播clearNonBondedDevices 压根不执行;
  • (b) onDeviceUnpaired 被 CSIP/HiSync 门控排除普通经典蓝牙手机,缓存不清;
  • (c) DeviceFoundHandler 对 cached + BOND_NONE 设备既不走 addDevice 也不走 dispatchonDeviceAdded 永不触发。

落点

  • BluetoothEventManager.java:514-524DeviceFoundHandler(★root cause)
  • :605-621BondStateChangedHandler
  • :613-616onDeviceUnpaired CSIP 门控
  • CachedBluetoothDeviceManager.java:92-115findDevice:123-141addDevice:250-260clearNonBondedDevices
  • BluetoothUnbondedDevicesPrefController.java:162-172/:278-298 — bugreport 0 命中 = 未执行

修复状态:未修。方案 A:BondStateChangedHandler BOND_NONE 分支无条件 removeCachedDevice;方案 B:DeviceFoundHandler 补 BOND_NONE 分支 dispatch(需去抖)。注意:即使合入 c4f59dd78(1922 的修复)也修不了本单——它未触碰 DeviceFoundHandler,反会加固残留。

教学点

  1. cached + BOND_NONE = 分发盲区:设备已在缓存(不走 addDevice)又未配对(不触发 onDeviceAdded 的 dispatch),两条路都堵死。这是 AOSP 原生设计的固有缺陷,不是小米改坏的。
  2. 与 1922 的鉴别诊断(面试级):transient 整列表清空(mPreferenceMap.size 突降后恢复)vs permanent 单设备隐藏(size 恒定)。同因(BOND_NONE 处理)异果(入口 Controller 活跃与否)。
  3. 广播的接收窗口依赖注册者生命周期——页面不活跃就错过,缓存状态机就断链。“错过广播”是事件驱动架构的固有税。

5. N801PROB-134132:蓝牙栏秒级卡顿 —「锁内禁止 binder」

现象:打开蓝牙栏很卡,6 次开关 6 次被顶退出,单次最长 3.136s + Skipped 207 frames

根因双向互锁——

  • 主线程直调 CachedBluetoothDeviceManagersynchronized 方法(clearNonBondedDevices / getCachedDevicesCopy
  • 同时 onPauseInternal(:226,全仓唯一)在后台线程投递清表,反过来持有同一把锁
  • 8/8 锁采样命中同一把锁;临界区内逐设备调 getBondState()——而它是同步 binder IPCCachedBluetoothDevice.java:908),台架设备密集 + BT 栈繁忙 + SYS_CPU_LIMIT 压 CPU 时,单次持锁秒级

落点

  • BluetoothUnbondedDevicesPrefController.java:169/:180 — 主线程 innerUpdateState 抢锁
  • :226onPauseInternal 后台投递 = 持锁源
  • :245/:331 — 冗余清表
  • f3dif CachedBluetoothDeviceManager.java:284锁内 binder:95findDevice 同锁第二入口(HungTask 栈实锤)
  • 对照组:BluetoothBondedDevicesPrefController.java:324-372 — 已修的正确范式

修复方案(三级):P0 = 清表/取数移后台线程 + 删 :245/:331 冗余清表;P1 = 收窄 CBDM:284 临界区(getBondState 过滤与 release 移出 monitor,必须同修否则治标不治本);P2 = refreshUi 加 200ms 去抖。

(2026-09-10 更新——修复已推 Gerrit,实际落地形态与三级方案有出入):

  • 367764(rls-xcd-u-mp-26-v6.0,commit 7106e69f3,5 文件,dcddif+xcddif)/ 367819(dev,commit 74d5e4847,7 文件含 f3dif),同 Change-Id,均未合入。
  • P0 落地::245(onDestroy 冗余清表)整行删除:331 拦截分支未删——先复位拦截标记、清表移后台(未采纳”增量单设备移除”,保留原语义零风险);:169 清表与缓存重建循环移后台,应用前二次检查 BONDING。
  • P1 落地:clearNonBondedDevices 重构为三段式——锁内纯内存快照(列表+member/sub+mBondTimestamp)→ 锁外逐设备 getBondState binder → 锁内纯内存应用;应用前比对 mBondTimestamp,快照后配对态翻转的设备跳过(闭合”快照→应用”窗口 NONE→BONDED 误删新配对的竞态)。配套:xcddif/f3dif mBondTimestamp 改 volatile;dcddif 原本无此字段,补 volatile 字段+getter+onBondingStateChanged 两处写入。
  • P2(refreshUi 200ms 去抖)未做,不在两笔 change 内。

第二轮实锤(08-18 复现,静置过夜后点 X 卡死 ANR):点 X → onBackPressed → ON_DESTROY 生命周期分发 → onDestroyInternal:223(设备分支行号)主线程同步清表 → 锁内 binder 不返 → 输入 5s 超时 ANR → 用户杀进程;平台 HungTask.MonitorUiProbe 在被杀前 0.8s 抓到主线程现行栈逐帧闭合。:223 正是 367764 整行删除的那行,修复闭合性充分。点 X 前主线程已持续 24 分钟逐设备枚举风暴(getAdapterDefaultProfileMatched 220~358 次/分钟,双栈 framework,每台一次 binder)。

BtTrace 全链路埋点实测(2026-09-10,fermi 台架 ~195 台缓存,埋点分支 dev-bt-trace-134132,点位清单见 jira-data/N801PROB-134132/analysis/TRACE-INSTRUMENTATION.md)

  • 锁互锁直拍:onPause 后台清表持锁 4329ms → 主线程 innerUpdateState 等锁 4118ms、整段卡 4141ms——刷新本案例峰值(原 3.136s)。
  • 60s 滑窗热点:CBD.getBondState 751 次(binder 热点 No.1)、CBDM.findDevice 502、ACTION_FOUND 276、addDevice.created 218(=台架每分钟新发现 218 台)。
  • 慢调用长尾:单台 FOUND 处理主线程最坏 836ms;onPause 后台清表 990ms。

遗留优化方向(本案例外溢,均未修)

  1. LocalBluetoothManager 构造传 handler=null → 全部蓝牙广播回调(含 ACTION_FOUND 风暴)跑主线程——风暴上主线程的总入口,迁移代价待评估;
  2. refreshDeviceUi 每次属性变化全量重查(isBusy/isConnected×2/getBondedDevices 全量),~24 次/分钟 × ~10 笔 binder;
  3. Bonded 侧 bgFetch 的 bondedDeviceFilter 每台 getAdapter().getBondedDevices() 全量 binder;
  4. CBDM.onBluetoothStateChanged(STATE_TURNING_OFF)(f3dif :336-366;xcddif :314-341 三处已验证)锁内逐台 getBondState——同类病灶,仅关蓝牙触发、不在本案例场景,367764/367819 未覆盖,建议单独立项。

教学点

  1. synchronized 临界区内禁做 binder IPC——铁律。临界区里每一行都要问”这行会不会出进程”。
  2. 取证方法论:dvm_lock_sample(双向记录 holder/waiter)+ HungTask 看门狗栈,是锁竞争的实锤证据,比”看起来卡”硬一万倍。
  3. mProfileLock 等锁字段(62 §2)的设计意图是保护列表一致性,不是给”顺便在锁里干活”用的。
  4. 同页面双控制器(Bonded/Unbonded 两个 PrefController)“一改一漏”——改并发问题必须找同类调用点全量对齐。

6. MANNPROB-2960:双蓝牙芯片已配对消失 —「缓存注入路径」

现象:fermi 车机(双蓝牙芯片)蓝牙音乐页点”添加设备”,“已配对的设备”分组空白;关开蓝牙后恢复

根因:fermi 有两个蓝牙芯片(hci0 默认 + hci1 ext,对应 bluetooth_manager / bluetooth_manager_ext 双服务、com.android.bluetooth1 ext 栈进程)。手机 bond 落在 ext adapter,而 A17 f3dif settingsLib 是纯单 adapter 实现

  • 全量回灌readPairedDevices() 只读默认 adapter(LocalBluetoothAdapter.java:127-128,f3dif 无 MICAR-PORTING 合并段)
  • 增量回灌:靠 BOND_STATE_CHANGED 广播,但 ext adapter 的广播从未扇出到 app 进程

铁证:BondStateChangedHandler cache miss 时的无条件 W 日志全 bugreport 0 条——“应然存在的日志零命中 = 事件从未送达”。设备自配对起从未进 Settings 缓存,列表自然永不渲染。

结论:framework bug 非 App——主修在 framework 双蓝牙层(ext 广播扇出 + getBondedDevices 聚合语义 + AdapterExtService 重启通道重建);本仓可选兜底 = 页面 onStart 重同步 readPairedDevices

教学点

  1. CachedBluetoothDeviceManager 的缓存有两条注入路径:① 构造/STATE_ON 时的全量回灌(readPairedDevicesBluetoothEventManager.java:202-218LocalBluetoothProfileManager.java:320-322);② 运行期增量广播。任何一条断了,表现为两种完全不同的故障(全空 vs 增量丢失)。
  2. “关开蓝牙恢复”是重要信号,但本案它只收敛出两个候选解释(root-cause 标记为待取证,勿当定论背):① STATE_ON 全量重读生效——但这暗示 framework app 侧 getBondedDevices() 聚合了 ext adapter,与服务端 dumpsys 默认 adapter Bonded=0 存在张力;② toggle 重绑 ext 广播通道致恢复。且 bugreport 抓取前无 toggle,恢复路径在素材内不可验证。教学价值在于:恢复动作给出待裁决的分叉,而不是现成答案。
  3. 判责分层:App 如实消费单 adapter 数据,无责;根因在服务提供方。写分析报告时要敢说”这不是我们的锅”,但要用日志证据说话。

7. MANNPROB-3960:误弹”设备替换”弹框 —「两套账务」

现象:开关蓝牙后回连两台设备,点第 2 台(12 Pro)弹”设备连接已达上限”替换弹框;视频铁证当时仅 1 台已连接

根因:mcb(蓝牙团队 ROM 服务)的 endpoint/设备条目生命周期泄漏——iPhone 经 CarPlay picker 获得”新发起配对”首位槽位,断开完成后条目未从活动 endpoint 列表移除("Device already exists" 日志实锤残留)。mcb 自报连接账务 connectedDevices=[]endpoints=3,以陈旧 endpoint 数判超限 → 误弹。Settings 如实渲染 mcb 数据。

教学点

  1. endpoint 账务与真实连接账务是两套可背离的计数——count != 实际连接数,判并发必须看时间窗交集,不能信单一计数字段。
  2. UI 如实反映服务侧脏数据时不算 UI 的责(同 MANNPROB-3499 先例:状态残留属服务侧,UI 不兜底不算责)。责任分层意识是转单/定案的依据。
  3. 本案无 file:line 落点(转单定案短格式),是”App 无责转出”类案例的典型形态——分析产出是日志证据链 + 转单建议,而非代码修改。

8. MANNPROB-2846:对讲机配对弹窗闪现 —「SDP 门控与豁免判据」

现象:对讲机连接后弹「在”小米对讲机3-41:9a:c1”上确认配对」(仅取消键),~0.3s 自动消失,配对成功(偶现)。

根因(两层):

  • 为什么闪:CarSettings 对 client 发起 + Just Works(CONSENT)的配对请求”先起确认页、Fragment 创建瞬间即 onPair() 自动确认”——页面诞生即自我作废;而 fresh bond 的 BOND_BONDED 广播被 SDP Discovery 结构性门控 ~350ms(enq 发出在 SDP 完成后 4ms),远晚于首帧 +118ms,闪现在该路径不可规避。
  • 为什么偶现:前两次 PAIRING_REQUEST 死于 L58 CoD 过滤器把”未知(0x1F00)“当”不支持”(出局式 createBond 配对前 CoD 未知是正常时序),第三次学到真实 CoD 才放行。

落点

  • MiBluetoothPairingRequest.kt L58-60(CoD 过滤)、L62-65(手柄免弹窗先例:直接 onPair return 不起页)、L85-102、L108(无条件 startActivityAsUser
  • MiBluetoothPairingDialogFragment.kt:180-182isRequestPairAsClient 创建瞬间 onPair
  • MiBluetoothPairingController.kt:103-113getDialogType(CONSENT 与 PASSKEY_CONFIRMATION 统射同桶)、:265-269
  • MiBluetoothUtils.kt:37,380-384isTwoWayRadio 已存在但广播路径零引用

修复方向::62 手柄分支旁新增 isTwoWayRadio + isPairingAsClient + CONSENT 三重限定直接 onPair return(不放宽为通用 JustWorks,防 PASSKEY 数字比较降级 = 安全回归);配套修 L58 CoD 未知放行。

教学点

  1. 免弹窗豁免判据的正确维度是”这个请求有没有人需要确认”,而非”设备是不是手柄”——判据选错维度,下次新设备类型来了还会再犯。
  2. 豁免逻辑只能放在起 Activity 之前MiBluetoothPairingRequest 层)——首帧永远跑不赢被 SDP 门控的 dismiss。
  3. BONDED 广播被 SDP 延迟是框架级时序事实(本库金标准:最多延迟 3s,判 bond 要用 getBondState()),配对链路的 UI 时序都要带着这个前提设计。
  4. 配对前 CoD 未知 ≠ 不支持:出局式(本端 outgoing 发起、配对前未经 Inquiry)配对时设备还没来得及广播自己的设备类。(注意”出局式”≠ 蓝牙安全模型里的 out-of-band/OOB——那特指 NFC 等带外密钥通道,与本案无关。)

9. MANNPROB-4867:详情弹窗”断开连接”按钮置灰脉冲 —「isBusy 无 profile 过滤直驱 UI」

现象:fermi/A17(modenaL),蓝牙设置→设备详情弹窗→关闭「同步通话记录和联系人」开关,「断开连接」按钮闪一下(置灰 ~0.9s 后恢复),必现。装机 1.0.0.231-dev 已含 3205 修复仍复发

根因:详情弹窗按钮 enabled 由 BluetoothOperationsController.java:99 setPositiveBtnEnable(!isBusy()) 无防抖、无 profile 过滤直驱;isBusy()(f3dif CBD:1042-1053)遍历 mProfiles 逐 profile 活 binder getConnectionStatus,任一 profile CONNECTING/DISCONNECTING 即 true。关 PBAP 开关 → setConnectionPolicy(FORBIDDEN)(45.521)→ PBAP 2→3→0(45.625→46.548,下载 3962 条中 abort 拉长窗至 923ms)→ LBPM StateChangedHandler(:449)refresh()dispatchAttributesChanged → 按钮回调(45.706 日志)→ SwitchLoadingHelper.setLoading(true)(MINIMUM_LOADING_TIME=600ms 只保最短展示,不构成防抖)→ 46.640 setLoading=false 置灰解除——0.93s 置灰脉冲日志实锤(setLoading=true 行被 ContactsProvider V 洪峰吞掉,靠 mIsLoading=true 反证)。

反转点:PBAP 开关行自身不抖,但原因不是设计良好而是缺陷——BluetoothDevicePreferenceController.onStartInternal:92-97 仅在 mCachedDevice!=null 时注册 refreshUi 回调,而 Fragment onResume 才 setCachedDevice(时序 onStart→onResume)→ 开关行回调从未注册(断开窗口内开关行零 updateState 日志实证)。是幸事也是 P1 独立缺陷:弹窗开着时手机侧改电话本权限,开关将不同步。

处置(2026-09-09 经办人裁决):定性设计如此关单,不改代码——按钮断开的是含 PBAP 的整机连接,过渡期置灰防重复操作被认定为自洽保护。原修复方案降级为可选优化项(按钮 busy 判定排除 PBAP 瞬态,保留 HFP/A2DP 拆链置灰),交产品裁决;P1=基类注册时序缺陷(setCachedDevice 补注册)单列跟进。

教学点

  1. isBusy 是”全家桶”语义——含所有 profile 的过渡态(含 PBAP 这种弱连接语义 profile)。拿它直驱某个具体 UI 元素的 enabled/置灰前,先问”这个元素该不该对 PBAP 瞬态反应”。3205 修列表、4867 爆弹窗:同一数据源的第二处消费点不会被第一处的修复顺带治好,修 isBusy 驱动类 bug 要全仓 grep 消费点。
  2. 「值变才通知」纪律不覆盖大事件:setter 级有值变守卫,但 onProfileStateChanged 这类大事件经 LBPM:449 refresh() 无条件 dispatch——每条 profile 状态迁移都会拍醒全部设备级回调注册者,UI 侧要自备过滤。
  3. 回调注册时机缺陷可以伪装成”稳定”:开关行不刷新在本案是良性表象,但断链证据(窗口内零 updateState + getConnectionPolicy 全是 CV_PhoneCall 发起)显示它从没工作过——“没出问题”≠“链路正常”,终审要用打点实证回调链真的通。
  4. 排障铁律复用:logcat > 降质手持录屏(960x540 反光视频测不出 MaterialButton disabled tint,日志 mIsLoading 翻转才是 ground truth)。

10. 案例知识点矩阵

案例属性/状态语义缓存清理回调接收窗口回调按设备过滤双蓝牙芯片主线程锁/锁内binder数据源一致性配对流程/广播时序取证方法
2403 图标退化●核心
3187 “已保存”闪烁●核心●核心
1922 删后空窗●核心
3492 永久隐藏●核心
134132 蓝牙栏卡顿●核心
2960 已配对消失●核心●核心
3960 替换弹框●核心
2846 配对闪现●核心
4867 按钮置灰脉冲●核心

(●=核心教学点;△=涉及但非主因)

11. 修复状态与时效声明

案例状态(截至各分析文档落笔时点)
3187已修:362953 合入 dev_a17_0610(8d4be76f6)与 dev(65aa6bfcf);364728 需随版
2403已提修:Gerrit 354880(快照字段方案);JIRA 仍开放,建议降级跟进
1922 / 3492 / 2846未修(修复方案已定案)
134132已推 Gerrit 未合入:367764(rls-xcd-u-mp-26-v6.0=7106e69f3)+ 367819(dev=74d5e4847,同 Change-Id);CBDM 三段式+mBondTimestamp 翻转防护;P2 去抖未做(2026-09-10)
2960转单 framework(双蓝牙层)
3960转单蓝牙团队(mcb 服务)
4867设计如此关单(经办人裁决 2026-09-09:过渡态置灰防重复操作=设计保护;可选优化项交产品);3205 修复在场仍复发=覆盖面缺口

⚠️ 行号与修复状态均以 jira-data/{KEY}/analysis/root-cause.md 落笔时点为准(2026-07 ~ 2026-08)。2403 的落点摘自其 comments.json 内 2026-07-16 根因分析评论(该目录无 analysis/ 子目录);3960 为转单定案短格式、无代码行号。复核行号请以当前分支实际代码为准。