42 · 踩坑与反直觉设计汇总
来源:连接知识库对抗审查记录 + 本轮 14 篇文档调研/对抗发现。每条:现象 + 原因 + 怎么办 + 锚点。 适用范围:改蓝牙代码、排蓝牙 bug 前必读的”反直觉清单”。持续追加。
1. 死代码清单(别误以为在用)
1.1 BluetoothDisconnectConfirmDialogFragment 是死代码
- 现象:以为是”断开确认对话框”实现。
- 原因:全仓库 grep 除类内
newInstance自引用外零外部调用,AOSP 遗产。 - 怎么办:实际断开确认走
AlertDialog.MiCarBuilder内联(BluetoothBondedDevicesPrefController.java:221-244showDisConnectConfirmDialog)。别去改这个死类。
1.2 isDeviceClassTypeSupport condition2 AUDIO_VIDEO 系列分支是死代码
- 现象:
MiBluetoothUtils.ktcondition2 的AUDIO_VIDEO_CAR_AUDIO→false(:159) 和耳机AUDIO_VIDEO_WEARABLE_HEADSET/HEADPHONES→false(:161-164)。 - 原因:condition1 已把
AUDIO_VIDEOmajor 判 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 异步。
- 原因:
BluetoothSwitchControllersetLoading(true)+setChecked(newValue)先推 UI,再adapter.enable();mUIBluetoothSwitchState(IDLE/SWITCHING) 防页面回前台 UI 跳变。 - 怎么办:排”开关 loading 不消”先查
onStartInternalIDLE 重刷逻辑。
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 LTK(
smp_calculate_long_term_key_from_link_key@45.087)+ 存 IRK。 - 怎么办:表述”BR→LE 派生”,源是 Link Key、产物是 LTK/IRK。
3.3 🚨 44.855 落 Link Key,45.087 派生 LTK(勿合并)
- 现象:root-cause 早期版本把 45.087 当”link key 落盘”。
- 原因:见 3.2,两者差 232ms,不同动作。
- 怎么办:时间线拆两行。
3.4 PAN isAutoConnectable=false(常被误记 true)
- 现象:误以为 PAN 自动连。
- 原因:
PanProfile.javaisAutoConnectable()三 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.java0 个 MICAR-PORTING 标记(xcddif 33/dcddif 10);CachedBluetoothDevice无isCloseAutoConnectDevice、无半关、timeout 不翻倍(用大常量 MAX_UUID 35000 vs 5000);另BluetoothEventManager.java也无 try/catch 广播保护(xcddif :387-389/:411-415 独有,f3dif/dcddif 无,单 handler 抛异常会丢后续广播)。 - 怎么办:改 A17 蓝牙逻辑看 f3dif,勿套 xcddif 定制假设。见 22、21 §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 已修复。
- 原因:
7239803be在dev_a17_0610被 reset,git merge-base --is-ancestor返回非祖先、git fsckdangling。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.conf(hci1_前缀),按 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-387,MIUI 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)后缀不一致——grepIfProcessed漏掉前者(BEM:573 调的正是 IfProcess 版)。 - 怎么办:检索用前缀
IfProcess可同时命中两者。
7.5 本轮锚点速查(红队核对版)
| 事实 | 锚点 |
|---|---|
| clearNonBondedDevices 调用点恰 4 处 | BluetoothUnbondedDevicesPrefController.java:169(innerUpdateState)/:226(stopDiscovery)/:245(onDestroy)/:331(拦截Add后);MiBluetoothPickerDialogFragment.kt:119 是注释非调用 |
| clearAllDevices 零调用、仅 xcddif 定义、无 release | CBDM: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 无锁 |