23 · BluetoothCallback 契约与接入指南
定位:21(架构)与 51 §4(回调↔广播三 flavor 总表)的操作手册——怎么接入、怎么扩展、哪里会踩坑。回调全表本文不重复,查 51。 基线:dev 分支 2026-09-06(对抗记录见 _对抗审查记录.md 第四轮)。
0. 一句话
BluetoothCallback 是 settingslib 事件总线到 UI 的观察者契约:BluetoothEventManager 收到系统广播后遍历 mCallbacks 调 onXxx,UI 类实现接口、按需覆写。
1. 设计语言(读 AOSP 的通用功)
- default 空实现(Java 8+):全部方法空体,实现类只覆写关心的——接口膨胀不惩罚实现者。方法数三 flavor:dcddif 10 / xcddif 15 / f3dif 11(对照表见 51 §4)。
- @IntDef 编译期约束(xcddif 版):
@interface ConnectionState(:175)、@interface AdapterState(:184) 把 int 参数锁到常量集——枚举的安全性 + int 的零开销,AOSP 惯用法。dcddif 版无任何注解(裸 int);f3dif 版有注解但Nullable来自 androidx 而非 android.annotation。
2. 事件五跳(谁在调你的回调)
蓝牙协议栈 → 系统广播 → BluetoothEventManager 的 Handler 解析
→ 遍历 mCallbacks(:71,声明 Collection<BluetoothCallback>,实例化 CopyOnWriteArrayList)
→ callback.onXxx(...) ← 你的代码
锚点(xcddif BluetoothEventManager.java):registerCallback :204(add :205)/ unregisterCallback :209(remove :210);dispatch 例:onDeviceAdded :265、onProfileConnectionStateChanged :278、onBluetoothStateChanged :447。
- 注册/注销必须配对(CopyOnWriteArrayList 只增不减 → 泄漏)。注册时机:蓝牙主页 Fragment 的 onStart/onStop、各 Controller 的 onStartInternal/onStopInternal(具体行号见 51 §4,以现场 grep 为准)。
- 所有回调在主线程触发,可直接刷 UI。
3. 实现者全景(2026-09-06 全仓核实,共 6 个)
⚠️ 纠正一个易错认知:实现者不止 BluetoothPreferenceController 一个。
| 实现者 | 源集 | 位置 | 覆写要点 |
|---|---|---|---|
BluetoothPreferenceController | main(单份) | settingsPage/micarConnectionSettings/.../controller/BluetoothPreferenceController.java:50 | 8 个回调(§4) |
BluetoothMainEntrySettingsFragment | main(单份) | settingsPage/micarConnectionSettings/.../view/BluetoothMainEntrySettingsFragment.java:56 | 蓝牙主页 Fragment |
BluetoothEventManagerExt.kt | 仅 f3dif | .../settingslib/bluetooth/BluetoothEventManagerExt.kt:28 | object : 写法;onProfileConnectionStateChanged(A17 事件增强) |
LocalBluetoothManagerExt.kt | 仅 f3dif | .../settingslib/bluetooth/LocalBluetoothManagerExt.kt:28 | object : 写法;onAudioModeChanged |
PresetUiController.kt | 仅 f3dif | .../hearingdevices/ui/PresetUiController.kt:66 | 助听设备预设 UI(字段持有匿名实现) |
AmbientVolumeUiController.java | 仅 f3dif | .../hearingdevices/ui/AmbientVolumeUiController.java:63 | 助听设备环境音量(多接口 implements 列表中的一员) |
找实现者的正确姿势:别只 grep
implements BluetoothCallback——Kotlinobject : BluetoothCallback、implements A, B, BluetoothCallback多接口列表、字段类型声明BluetoothCallback cb = ...都会漏。至少组合搜implements BluetoothCallback、object : BluetoothCallback、BluetoothCallback>三种模式(第四轮对抗审查正是用单一模式漏掉了 5 个实现者)。
4. BluetoothPreferenceController 覆写清单(8/15,行号 2026-09-06)
onBluetoothStateChanged:137 / onScanningStateChanged:142 / onDeviceAdded:146 / onDeviceDeleted:150 / onDeviceBondStateChanged:154 / onConnectionStateChanged:158 / onActiveDeviceChanged:162 / onAudioModeChanged:166。均为一行委托刷新,模式统一,可作新 Controller 的模板。
5. 扩展三步(新接一个事件的标准路径)
BluetoothEventManager注册广播 + Handler 里 dispatchcallback.onXxx(...);BluetoothCallback加default void onXxx(...) {};- UI 实现类覆写 + 注册/注销配对。
flavor 同步矩阵:接口与 dispatch 在三 flavor 是独立拷贝,成对增删。反面教材即正面教材:f3dif 移除 5 个回调(A2dpCodec/CSIP 组×2/Auracast 广播×2)时,接口与 EventManager 分发一起删(分发零命中),同时新增 onAutoOnStateChanged(接口 :174、dispatch BluetoothEventManager.java:640)——接口有方法而无人 dispatch = 永不触发的死回调;有 dispatch 而接口无方法 = 编译不过。改一处漏 flavor = 编译不过或行为不一致;dcd(dcddif 10 法)最小可作最小集参照。
6. 陷阱清单
- 两套 STATE 数字体系:
BluetoothAdapter.STATE_CONNECTED(本接口 :19-26 静态导入,@ConnectionState用这套)vsBluetoothProfile.STATE_CONNECTED(profile 层)。数字可能相同,语义来源不同——比较/存库时别混。 - bond ≠ connect:配对持久(重启还在)、连接临时。见 10、61。
- 三层连接态:
onConnectionStateChanged(整机有无连接的边界)/onProfileConnectionStateChanged(某设备某 profile)/onAclConnectionStateChanged(ACL 物理链路)。车机”连接成功”UI 通常看 profile 层。深讲见 61。 - BONDED 广播被 SDP 延迟最多 3s(20 §2)——
onDeviceBondStateChanged不是实时信号,判态用getBondState()。 - f3dif 特有:
onAutoOnStateChanged(:174) 为 A17 独有;Auracast/codec/CSIP 回调整体移除,事件走别的机制(Auracast QR 侧见 80 系列)。
时效声明
行号 2026-09-06 于 dev 分支核对(主控 + 红队交叉,见 _对抗审查记录.md 第四轮)。行号漂移是常态,引用前 grep -n 复核,必标 flavor。