66 · bond 状态与自动回连 — 状态机、耐心窗口与黑名单
源码基线 f3dif。交叉引用:11-配对与SSP安全机制(配对协议细节)、21-settingslib架构与事件流 §自动连接、31-连接与自动连接策略、68 §8 2846 本篇讲清:配对状态机的三个分支各自做什么、自动回连的时间窗怎么算、以及 A17 上黑名单为何失效。
0. 状态机鸟瞰
createBond() 配对流程出错/用户移除
┌─────────┐ ─────────────→ ┌─────────────┐ ─────────────→ ┌────────────┐
│BOND_NONE│ │BOND_BONDING │ │ 回到 NONE │
│ (无关系) │ ←───────────── │ (握手中) │ └────────────┘
└─────────┘ 握手失败 └──────┬──────┘
↑ │ 握手成功(拿到 Link Key)
│ removeBond()/unpair() ↓
│ ┌───────────┐
└────────────────────── │BOND_BONDED│ (关系存续,无需 ACL 在线)
└───────────┘
广播入口:ACTION_BOND_STATE_CHANGED(BEM:127 注册)→ BondStateChangedHandler(BEM:407-509)→ cachedDevice.onBondingStateChanged(bondState, prevBondState)(:450 → CBD:1147-1210)。注意 f3dif 的这个方法带 prevBondState 参数(老版本不带——三 flavor 签名对照见 21 篇 §8)。
1. BondStateChangedHandler:进 CBD 前的六道关卡(BEM:407-509)
按执行顺序,每一道都是真实 bug 的现场:
| # | 代码 | 作用 | 关联案例 |
|---|---|---|---|
| 1 | :409-412 | device null 防御 | — |
| 2 | :416 mDeviceManager.onBondStateChangedIfProcess(CBDM:567) | CSIP 组成员的 bond 变化不单独刷 UI(组内代管) | — |
| 3 | :421-432 | 小米补丁 MANNPROB-2291:BOND_NONE 且 EXTRA_PAIRING_CONTEXT==PAIRING_CONTEXT_REPAIRING 直接 return——autonomous repair 的中间态 BOND_NONE 不当失败显示(常量是手抄的平台 @hide 值,:59-66) | 2414 同机制 |
| 4 | :434-439 | findDevice 为 null → addDevice 补建档案(诞生入口之一,63 §2.1) | 3492 |
| 5 | :441-443 | BONDED 时按 identity address 去重(LE 隐私地址场景) | — |
| 6 | :445-447 → :450 | 回调 onDeviceBondStateChanged(BluetoothCallback)→ cachedDevice.onBondingStateChanged(...) | — |
尾部 :466-470 → showUnbondMessage(:480-508):BOND_NONE 且非诊断模式时按 EXTRA_UNBOND_REASON 映射失败 toast。
2. onBondingStateChanged 三个分支逐行(CBD:1147-1210)
2.1 BOND_NONE(:1148-1183)— 大扫除
synchronized (mProfileLock) { mProfiles.clear(); } // :1149-1151 ⚠️ mRemovedProfiles 不清!
// MIUI MOD: BT_MIUIBluetoothFrame(:1152-1182,后台线程):
// phonebook/message/SIM 三权限重置 ACCESS_UNKNOWN(:1159 同时 mBondTimestamp = null)
// prevBondState==BOND_BONDING 且诊断flag → mBondFailureTimeMillis 记时(:1163)
// → 调度 CAN_NOT_PAIR_TIME_OUT_MILLS 延迟 refresh(:1170-1175)三个教学点:
mProfiles.clear()但mRemovedProfiles不清——残留的 removed 列表是日志噪音源,也是”明明解绑了还有 profile 痕迹”的困惑来源。- 三权限重置在后台线程做(权限重置含 IO,不占主线程)——f3dif 无
MICAR-PORTING标记但有MIUI MOD标记(小米改动换了标记体系)。 mBondFailureTimeMillis只在 BONDING→NONE(握手失败)时记录,供”配对失败”延迟提示节流。
2.2 BOND_BONDING — 无操作
状态机里没有独立分支(不进任何 if)——握手期间一切交给 framework。
2.3 BOND_BONDED(:1189-1205)— 记账 + 自动连接
mBondTimestamp = new Timestamp(...); // :1190 配对成功时刻
if (isBondingInitiatedLocally()) { // :1192 ★只有"本端发起"的配对
connect(); // :1194 才自动连接
}
HearingDeviceStatsLogUtils.addToJustBonded(...); // :1198 助听指标
mBondFailureTimeMillis = -1; // :1200-1204 复位(诊断flag下)isBondingInitiatedLocally() 是自动连接的第一道闸门:本端用户点”配对”→ 成功即连(用户体验闭环);对端发起的配对(比如从手机侧发起)成功后不自动连——尊重本端用户的选择权。
⚠️ f3dif 与 dcddif/xcddif 的关键差异:老 flavor 在 :1192-1194 这里还有第二道闸门——
isCloseAutoConnectDevice()黑名单(DC:852-858 / XC:1078-1084,读Settings.Secure bluetooth_close_autoconnect,CarPlay 配对后写入以禁止自动连 HFP/A2DP)。f3dif 没有这个方法也没有这个判断——黑名单写入仍在(disableAutoConnect所有 flavor 都写),但 f3dif 没人读,A17 上 CarPlay 黑名单失效(知识库金标准”假设 H6”,详见 31 §4)。
3. 自动回连的第二机制:UUID 时间窗(ACL 记账 + 耐心窗口)
§2.3 的自动连接只覆盖”配对刚成功”。另一场景是:已配对设备回连时,ACL 先通、UUID(SDP 结果)后到——连接发起时 mProfiles 可能还是空的(64 §2.2 直接放弃)。谁来回连?答案是一条”账本 + 窗口”机制:
3.1 记账:onAclStateChanged(CBD:1212-1262)
入口:ACTION_ACL_CONNECTED/DISCONNECTED(BEM:149-150 注册,AclStateChangedHandler :566-613)
读 EXTRA_TRANSPORT(默认 BREDR,:608-609)
写入(CBD.onAclStateChanged):
首个 ACL 连接(BR/EDR 与 LE 都未连时):
mConnectAttempted = SystemClock.elapsedRealtime() // :1226-1240 记账★
LE/BREDR 双通道标志分别更新 // :1243-1247
双通道都断开:mConnectAttempted = -1 // :1249-1261 销账
⚠️ 字段注释与实现不符(62 §6 详述):注释说”上次自动连接尝试时间”,实际记的是 ACL 链路建立时刻。半断(一条传输还连着)不清账;换壳时账本随迁(:2580)。dcddif 版还漏了
-1守卫(判语句 DC:811)——f3dif 补上了。
3.2 消费:onUuidChanged(CBD:1109-1145)
入口:ACTION_UUID → UuidChangedHandler(BEM:520-526);PbapClientProfile.java:86 也直调
步骤:
1. updateProfiles()(:1056)—— 用新 UUID 重建 mProfiles(LBPM:634 干活)
2. 选窗口(if-else 链,:1113-1120):
含 HOGP → 60_000 // :102
含 HEARING_AID → 45_000 // :101(注释:第二只耳做服务发现更慢)
含 LE_AUDIO → 60_000 // :103
默认 → 35_000 // :99
3. 决策(:1138-1142):
if (mConnectAttempted != -1
&& mConnectAttempted + timeout > SystemClock.elapsedRealtime()) {
connectDevice(); // ★窗口内 UUID 到达 → 补发连接
}
4. dispatchAttributesChanged()(:1144)
时间线图解:
时刻0 时刻0+2s 时刻0+? 窗口边界(35s默认)
│ ACL连上 │发起连接: │UUID终于到达 │
▼ ▼mProfiles空,放弃 ▼ ▼
●────────────●─────────────────────●━━━━━━━━━━━━━━━━━━━╳
mConnectAttempted 落在窗口内?→connectDevice()
= now(:1227) 落在窗口外?→什么都不做
窗口选择顺序敏感(HOGP > HEARING_AID > LE_AUDIO):同时含助听器与 LE Audio UUID 的设备拿 45s 而非 60s——if-else 链重排即改行为,改这段代码前先想清楚。
金标准背书:f3dif 21 处(三 flavor 全仓 60 处)
isAutoConnectable命中全是定义与覆写、零调用点——“自动连接”的实际机制就是本节的isBondingInitiatedLocally+ 时间窗两套,与接口上的语义标记无关(21 篇 已核实)。
4. unpair:取消配对的三件事(CBD:656-690)
public void unpair() {
mUnpairing = true; // :666 单向旗(永不复位,[62 §9])
// BOND_BONDING → cancelBondProcess();BONDED → removeBond()
// (两者选择逻辑见 [11 篇 §cancel 语义](../10-基础概念篇/11-配对与SSP安全机制.md)——取消握手 ≠ 删除关系)
LeAudioSharing → removeBroadcastSource(组成员集合) // 调用点 :667-676(方法定义 :691-708,@WorkerThread 删广播源)
releaseLruCache(); // :679 图标缓存清空
}UI 侧入口:详情页 BluetoothOperationsController Unpair → MIS 确认/DIALOG_DEVICE_UNPAIR → performUnpair(MiBluetoothExt.kt:55-62,:57 调 unpair())。unpair 后 CBD 不立即出表——残留到下次 clearNonBondedDevices(63 §5.3,68 §4 3492 的温床)。
5. 广播时序的两条金标准(排障必背)
- BONDED 广播被 SDP 延迟(最多 ~3s):
ACTION_BOND_STATE_CHANGED(BONDED)要等 SDP Discovery 结构完成才发出(68 §8 2846 实测 enq 在 SDP 完成后 4ms)。判断当前 bond 状态以getBondState()为权威(读服务侧实时态),别信”广播还没到 = 还没配对上”。 - 配对前 CoD 可能未知:出局式(手动发起)配对时,
PAIRING_REQUEST到达时刻对端设备类(CoD)还没学到——把”未知(0x1F00)“当”不支持”过滤掉是 2846 偶现的直接原因。
6. 自测
- BOND_NONE 分支清了哪些、没清哪些?(清 mProfiles;不清 mRemovedProfiles)
- 自动连接的两道闸门?(
isBondingInitiatedLocally;f3dif 上第二道黑名单闸门缺失) -
mConnectAttempted记的是什么时刻?什么条件下销账?(首个 ACL 建立;双传输全断才 -1) - 窗口选择的优先级顺序?为什么助听器反而比 LE Audio 短?(HOGP>HA>LE_AUDIO;AOSP 原序如此——HA 45s 是”第二只耳服务发现慢”的补偿,LE Audio 60s 是另一档)
-
isAutoConnectable参与自动连接决策吗?(不参与,零调用点) - 判断 bond 状态用什么方法最权威?为什么?(
getBondState()——BONDED 广播被 SDP 延迟最多 3s)
下一篇 67-active设备与多设备:多台设备并存时的”当前输出”、CSIP 组与电量。