42 · 踩坑与反直觉设计汇总

来源:连接知识库对抗审查记录 + 本轮 14 篇文档调研/对抗发现。每条:现象 + 原因 + 怎么办 + 锚点。 适用范围:改蓝牙代码、排蓝牙 bug 前必读的”反直觉清单”。持续追加。

1. 死代码清单(别误以为在用)

1.1 BluetoothDisconnectConfirmDialogFragment 是死代码

  • 现象:以为是”断开确认对话框”实现。
  • 原因:全仓库 grep 除类内 newInstance 自引用外零外部调用,AOSP 遗产。
  • 怎么办:实际断开确认走 AlertDialog.MiCarBuilder 内联(BluetoothBondedDevicesPrefController.java:221-244 showDisConnectConfirmDialog)。别去改这个死类。

1.2 isDeviceClassTypeSupport condition2 AUDIO_VIDEO 系列分支是死代码

  • 现象MiBluetoothUtils.kt condition2 的 AUDIO_VIDEO_CAR_AUDIO→false(:159) 和耳机 AUDIO_VIDEO_WEARABLE_HEADSET/HEADPHONES→false(:161-164)。
  • 原因:condition1 已把 AUDIO_VIDEO major 判 false(:144-145),res=condition1&&condition2 永远到不了 condition2 的 AUDIO_VIDEO 系列分支。
  • 怎么办:防御性冗余,删了也无影响;引用过滤逻辑只看 condition1。

2. 反直觉设计(预期行为,非 bug)

2.1 扫描列表只显示手机类设备

  • 现象:车机蓝牙扫描列表不显示耳机/音箱/车机自身/电脑。
  • 原因isDeviceClassTypeSupport:141-170 有意设计——车机蓝牙用于手机互联(电话/通讯录/CarPlay),不连蓝牙耳机听歌。
  • 怎么办:这是预期行为,勿当 bug 提。耳机配对走手机主动发起或 CarPlay 流程。

2.2 详情页只有 PBAP 一个 profile 开关

  • 现象:详情页极简,无重命名/无MAC/无HFP/A2DP开关。
  • 原因BluetoothDeviceProfilesPreferenceController 钉死 PbapClientProfile 单引用(非 PreferenceGroup);HFP/A2DP 由 CachedBluetoothDevice.connect() 自动连,UI 无开关。bluetooth_device_details_fragment.xml(AOSP原版) 不被加载,MiCar 用 micar_bluetooth_device_details_fragment.xml
  • 怎么办:加 profile 开关要改 Controller 的 getPreferenceType + XML 槽位,别动 AOSP 遗产布局。

2.3 开关”UI 先行”+ 防抖

  • 现象:点蓝牙开关,UI 立即切换,真 enable/disable 异步。
  • 原因BluetoothSwitchController setLoading(true)+setChecked(newValue) 先推 UI,再 adapter.enable()mUIBluetoothSwitchState(IDLE/SWITCHING) 防页面回前台 UI 跳变。
  • 怎么办:排”开关 loading 不消”先查 onStartInternal IDLE 重刷逻辑。

3. 协议/技术误解纠正(最高频踩坑)

3.1 🚨 CarPlay 不是蓝牙 profile

  • 现象:误以为 CarPlay 是蓝牙音频 profile。
  • 原因:CarPlay 是 Apple 私有协议,主数据走 WiFi;蓝牙只通过 EIR 私有 UUID 2d8d2466-...ConnectionUtils.kt:30-31)做设备识别。
  • 怎么办:讲 CarPlay 用”蓝牙握手跳板”,勿列入蓝牙 profile 表。

3.2 🚨 CTKD 方向是 BR→LE(非”LTK 经经典通道分发”)

  • 现象:误读 45.087 Save LTK 为”把 LTK 分发出去”。
  • 原因:BR/EDR Secure Connections 先生成 Link Key(44.855 落盘 key_type=0x8),SMP-BR 经 CTKD 从 Link Key 派生 LE LTKsmp_calculate_long_term_key_from_link_key @45.087)+ 存 IRK。
  • 怎么办:表述”BR→LE 派生”,源是 Link Key、产物是 LTK/IRK。
  • 现象:root-cause 早期版本把 45.087 当”link key 落盘”。
  • 原因:见 3.2,两者差 232ms,不同动作。
  • 怎么办:时间线拆两行。

3.4 PAN isAutoConnectable=false(常被误记 true)

  • 现象:误以为 PAN 自动连。
  • 原因PanProfile.java isAutoConnectable() 三 flavor 均返回 false(f3dif:85-87 / xcddif:87-89 / dcddif:88-90)。MAP/SAP/HID 自动连;PAN/OPP/HidDevice/PbapServer 不自动连。
  • 怎么办:见 12-Profile §1.4 表。

3.5 isAutoConnectable 不在 connect 链

  • 现象:误以为 settingslib connect 时过滤 isAutoConnectable。
  • 原因:它是 LocalBluetoothProfile 接口方法,框架层用;settingslib 走 mDevice.connect()/connectAllEnabledProfiles 把顺序交框架。
  • 怎么办:见 21 §3。

3.6 bond/ACL/profile 三层独立

  • 现象:混淆”配对=连接=能用”。
  • 原因:三层独立状态机。bond 只落密钥,ACL 是链路,profile 是业务。
  • 怎么办:见 10-协议速成 §6。

4. flavor 陷阱(改代码致命坑)

4.1 🚨 f3dif(A17 主线)= 纯上游 AOSP,MICAR 定制全部回归

  • 现象:A17 上改蓝牙逻辑,以为黑名单/半关/try-catch 还在。
  • 原因:f3dif CachedBluetoothDevice.java 0 个 MICAR-PORTING 标记(xcddif 33/dcddif 10);CachedBluetoothDeviceisCloseAutoConnectDevice、无半关、timeout 不翻倍(用大常量 MAX_UUID 35000 vs 5000); BluetoothEventManager.java 也无 try/catch 广播保护(xcddif :387-389/:411-415 独有,f3dif/dcddif 无,单 handler 抛异常会丢后续广播)。
  • 怎么办:改 A17 蓝牙逻辑看 f3dif,勿套 xcddif 定制假设。见 2221 §8。

4.2 🚨 f3dif 黑名单失效 → CarPlay 配对后自动连蓝牙

  • 现象:A17 上 CarPlay 设备配对后 HFP/A2DP 自动连,抢占通道。
  • 原因disableAutoConnect 所有 flavor 都写 Settings.Secure,但 f3dif 没人读(无 isCloseAutoConnectDevice)。
  • 怎么办:A17 排查此问题查 H6(见 31 §4),别去查 onUuidChanged 旁路。

4.3 同名同包不同实现、无继承

  • 现象:想在 dcddif extends xcddif 实现。
  • 原因:三 flavor sourceSet 编译期叠加,互不可见。
  • 怎么办:改 settingslib 三套都要看;本仓业务代码默认 src/main 单份(除 MiBluetoothCollinear.kt:dcddif 独立 + xcddif/f3dif 共用)。

4.4 onBondingStateChanged 签名三 flavor 不同

  • 现象:override 报错。
  • 原因:dcddif/xcddif 单参 (int)f3dif 双参 (int, int prevBondState)(:1147)。
  • 怎么办:改签名三套同步。

5. 行号/版本陷阱

5.1 🚨 2956 修复 commit 是悬空提交(bug 仍在)

  • 现象:以为 2956 已修复。
  • 原因7239803bedev_a17_0610 被 reset,git merge-base --is-ancestor 返回非祖先、git fsck dangling。onCancel(:283) 仍是旧版 cancelBondProcess
  • 怎么办:核实修复用 git merge-base --is-ancestor <commit> HEAD勿信 git branch --contains(受本地状态影响)。见 41 §6。

5.2 02 文档 onCancel:279/:281 已过时

  • 怎么办:当前 onCancel:283(仍是旧版),由 30-fix 更新。

5.3 行号区分 if-check vs call

  • 现象:双蓝牙”调用点”行号对不上 displayBluetoothDevicePicker
  • 原因:文档列的常是 if(size>=LIMIT) 判定行,非调用行。
  • 怎么办:用调用行 :90/:526/:142/:295/:111。见 32 §2。

5.4 DevicePairStageEvent 不是 stage 枚举

  • 现象:以为是配对阶段枚举。
  • 原因:是三元组(isBusy/isConnected/mBondState)瞬时值 + 边沿检测器,仅 BluetoothUnbondedDevicesPrefController 自身消费。
  • 怎么办:见 32 §4。

6. 排障反直觉

6.1 BONDED 广播 ≠ 配对刚完成(被 SDP 延迟最多 3s)

  • 现象:用广播到达当”配对完成”锚点关弹窗,结果被迟到广播坑。
  • 原因:BondStateMachine wait for service discovery UUIDs 先 SDP 再广播(20 §2)。
  • 怎么办:做决策用 getBondState()(读服务侧实时态),广播只用于 UI 刷新。

6.2 getBondState() 比 广播权威

  • 2956 修复方案成立的前提:46.336 时广播还没到,但 getBondState() 已 BONDED。

6.3 三对话框 taskAffinity 顶替(150ms 延迟之源)

  • 现象:CarPlay 弹窗为何延迟 150ms。
  • 原因:PairingDialog/PickerDialog/CarplayChoose 共用 taskAffinity=car.settings.bluetooth + singleTask,互相顶替,需等配对弹窗动画结束。见 02 §3。

6.4 cancelPairBySelf 全局静态坑

  • 现象:两配对弹窗并发(理论 singleTask 顶替)会互相覆盖该标志。
  • 怎么办MiBluetoothUtils.kt:52 全局静态,注意并发。

6.5 bt_config.conf 路径非标准

  • 现象:找密钥文件找不到。
  • 原因:小米双适配器 /data/misc/bluedroid/hci1_bt_config.confhci1_ 前缀),按 MAC 分段,无 [LinkKeys]/[LeKeys] 聚合段。

6.6 variant 命名鸿沟(native vs app)

  • 现象:logcat 看到 pairingVariant 0(native) 和 variant 2(app) 混淆。
  • 原因:btif 枚举 0=PASSKEY_CONFIRMATION;App BluetoothDevice 常量 2=PASSKEY_CONFIRMATION。BR/EDR SC 下都对应 Numeric Comparison UI。见 11 §3。

7. CachedBluetoothDeviceManager 坑位(2026-09-06 第五轮追加)

对象:base/settingsLibAndroid/src/xcddif/java/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java(xcddif 581 行;下文 CBDM 行号均为 xcddif)。9 条声明红队盲核 9/9 PASS(_对抗审查记录.md 第五轮)。63 章已记的(clearNonBondedSubDevices 首组 return、onDeviceDisappeared 死码)不重复。

7.1 xcddif 解配联动不对称(MIUI MOD 改弱了 AOSP 级联)★

  • 现象:解配 TWS/LE Audio 主设备(如左耳)后,CSIP 组员(右耳)仍保持配对,之后可能单只自动回连;反过来解配组员会连带解配主设备。
  • 原因onDeviceUnpaired(CBDM:375-418)中 AOSP 原版”主设备解配→遍历组员逐个 unpair”被注释(:382-387MIUI MOD: BT_LeAudio 标记 :381/:401),替换为温和版 clearMemberDevices()(:389) + 条件重置 groupId(:394)——只清缓存、不下发 unpair;而”组员解配→mainDevice.unpair()”分支(:403-406)未动、仍生效。不对称仅限 CSIP 组员——助听 subDevice 两方向都联动(:407-417,对称);f3dif(纯 AOSP)两方向都级联。
  • 怎么办:排障遇”解配了还自己连上/列表残留一只”,先怀疑 CSIP 组员残留(日志锚点 onDeviceUnpaired: ...set group GROUP_ID_INVALID :393);注意 63:200 的”级联解配对”描述是 f3dif 基线,xcddif 已分叉(63 章已加注)。

7.2 onScanningStateChanged 的同款首组 return(63 §5.2 的扫描路径补集)

  • 现象:开始新扫描时理论上要重置全部设备的 justDiscovered,实际多 CSIP 组场景只有第一个含组员的主设备之前的”段”被处理。
  • 原因:CBDM:305 处理完第一个非空 memberDevices 后 return——与 63 §5.2 已记的 clearNonBondedSubDevices:274 同款模式;f3dif 同在(:303/:327),AOSP 血统
  • 怎么办:读代码别把 return 当 continue;若修(改 continue)三 flavor 一起动并回归 TWS 扫描可见性。

7.3 clearAllDevices:xcddif 独有死码 + 无 release()

  • 现象:全仓零调用;且 dcddif/f3dif 连方法都没有——xcddif 独有。
  • 风险:与 clearNonBondedDevices(:256 移除前 release)不同,它只 remove(i)(:289)不 release()——若被调用会漏掉 CBD 的 handler 清理。
  • 怎么办:可删;留着也别在新代码里”顺手复用”。

7.4 IfProcess / IfProcessed 命名混用(grep 陷阱)

  • 现象onBondStateChangedIfProcess(CBDM:538)与 onProfileConnectionStateChangedIfProcessed(CBDM:352)后缀不一致——grep IfProcessed 漏掉前者(BEM:573 调的正是 IfProcess 版)。
  • 怎么办:检索用前缀 IfProcess 可同时命中两者。

7.5 本轮锚点速查(红队核对版)

事实锚点
clearNonBondedDevices 调用点恰 4 处BluetoothUnbondedDevicesPrefController.java:169(innerUpdateState)/:226(stopDiscovery)/:245(onDestroy)/:331(拦截Add后);MiBluetoothPickerDialogFragment.kt:119 是注释非调用
clearAllDevices 零调用、仅 xcddif 定义、无 releaseCBDM:286-291
onDeviceDisappeared 三 flavor 定义均零调用xcddif:70 / dcddif:56 / f3dif:73
addDevice 分叉不变式(组员/子设备不进主列表不 dispatch)CBDM:132-136
CSIP 组员配对静默建档+自动 connect(分支实为 !=BOND_NONE)CBDM:550-558
xcddif 锁结构18 个 synchronized 方法 + addDevice:126 块;onDeviceDisappeared static 无锁