20 · Android 蓝牙框架全景(新人第二课)
核心文件:本仓
settingsPage/micarConnectionSettings/.../bluetooth/+ AOSPpackages/modules/Bluetooth/(本机镜像/home/zbc/pangu/yu7/packages/modules/Bluetooth/,已核实源码) 适用范围:Android 蓝牙框架全景,排障看日志的地图 实证:MANNPROB-2956 bugreport(/home/zbc/下载/test/车机JIRA/jira-data/MANNPROB-2956/)
0. 为什么写这份
01-发现流程 只讲应用层扫描闸门;但排配对/连接 bug 时,bugreport 里全是 BluetoothAdapterService/bt_btif_dm/btm_sec/smp 这些 tag,看不出哪一层在叫。本文给一张全栈地图:从 BluetoothAdapter.createBond() 一路到 controller 的 HCI 包,每一层在日志里叫什么、出什么问题看谁。真实案例 MANNPROB-2956 横跨五层,并暴露新人 90% 踩的坑:广播不是实时信号。
1. 五层架构分层图
flowchart TB subgraph L1["① App 层(Settings/CarSettings)"] A1["BluetoothAdapter/BluetoothDevice(公开API)"] A2["settingslib: LocalBluetoothManager/CachedBluetoothDevice"] end subgraph L2["② system_server"] M1["BluetoothManagerService<br/>TAG: BluetoothManagerService<br/>管理 enable/disable、bind BluetoothApp"] end subgraph L3["③ Bluetooth App 进程(com.android.bluetooth)"] S1["AdapterService<br/>TAG: BluetoothAdapterService"] S2["BondStateMachine<br/>TAG: BluetoothBondStateMachine"] S3["RemoteDevices<br/>设备属性表/广播源"] S4["Profile Services: HFP/A2DP/PBAP..."] end subgraph L4["④ JNI + 协议栈(bluedroid,同进程 native)"] N1["BTIF (bt_btif_dm)"] N2["BTM (bt_btm_sec)"] N3["SMP (smp_*)"] end subgraph L5["⑤ HCI / Controller"] H1["HCI 命令/事件(snoop log 抓这层)"] end A1 -- "AIDL: IBluetooth(bind com.android.bluetooth)" --> S1 A1 -- "AIDL: IBluetoothManager(bind system_server)" --> M1 M1 -- "bindService 拉起 BluetoothApp" --> S1 S1 -- "JNI" --> N1 S3 --> S2 S2 --> S4 N1 --> N2 --> N3 N3 -- "HCI 命令" --> H1 H1 -. "HCI 事件回调" .-> N3
1.1 公开 API 与 binder 落点
App 开发者能用的全在 android.bluetooth.*(镜像 packages/modules/Bluetooth/framework/java/android/bluetooth/):
BluetoothAdapter:enable()/disable()/startDiscovery()/getBondedDevices()/getRemoteDevice()等。BluetoothDevice(每个远端设备一个实例):配对 API 全在这——createBond()(:2299)、createBond(transport)(:2322)、cancelBondProcess()(:2457)、removeBond()(:2490)、getBondState()(:2590)、setPairingConfirmation()(:3339)、fetchUuidsWithSdp()(:3115)。
binder 落点:
IBluetoothManager实现在 system_server 的BluetoothManagerService(真实路径packages/modules/Bluetooth/service/src/com/android/server/bluetooth/BluetoothManagerService.java,:4525发ACTION_STATE_CHANGED)。负责 enable/disable、绑定 Bluetooth App、转发开关广播。IBluetooth实现在com.android.bluetooth进程(AdapterService.java:202TAG=BluetoothAdapterService),uid=bluetooth。持有真正的协议栈句柄。
Mainline 模块(措辞纠正):Bluetooth 是 Project Mainline 模块,Android 13 起作为 optional(可选)Mainline 模块引入,Android 16 起 fully updatable & certified(Project Mainline 本身 Android 10 引入)。包名
com.android.bluetooth,APEXcom.android.btservices。MiCar 在此基础上做 MIUI/CarUI porting(源码MICAR-PORTING-START/END标记,如BondStateMachine.java的CARPLAY_128UUID)。
2. BondStateMachine:BONDED 之前为什么要等 SDP
2.1 它是什么
BondStateMachine.java(packages/modules/Bluetooth/android/app/src/com/android/bluetooth/btservice/BondStateMachine.java,833 行,TAG=BluetoothBondStateMachine 在 :72)。它是 Bluetooth App 进程里的 StateMachine,唯一负责把底层”链路密钥协商完成”翻译成应用层的 ACTION_BOND_STATE_CHANGED 广播。两状态:StableState(:144) / PendingCommandState(:233)。
2.2 关键设计:BONDED 广播被 SDP UUID 等待推迟
sendIntent() 方法定义在 :534,其中 SDP 延迟分支在 :581-596,关键 :583 infoLog(device + " is bonded, wait for SDP complete to broadcast bonded intent"):
if (newState == BOND_BONDED && devProp.getUuids() == null) {
infoLog(device + " is bonded, wait for SDP complete ..."); // :583
mPendingBondedDevices.add(device);
sendMessageDelayed(BONDED_INTENT_DELAY, sPendingUuidUpdateTimeoutMillis); // 3000ms(:86)
return; // 不发 BONDED 广播,等 UUID_UPDATE(:196-198) 或 3s 超时(:191-194)
}设计意图:BONDED 广播的下游(settingslib、CarPlay 识别)依赖设备 UUID(如 CarPlay 的 2d8d2466-...)。协议栈 BONDED 时 UUID 还没通过 SDP 拿到,框架主动延迟广播,等 UUID 再发。
2.3 2956 实证(毫秒级,bugreport 行号可回溯)
| 时间 | 事件 |
|---|---|
| 16:24:44.860 | 协议栈 BOND_BONDED(底层已成功,应用层广播未发) |
| 16:24:44.862 | BondStateMachine: is bonded, wait for service discovery UUIDs(即 :583) |
| 16:24:46.534 | 被延迟的 BONDED 广播发出(比底层晚 1.674s) |
| 16:24:46.336 | 用户点取消(落入延迟窗口) |
用户 46.336 点取消时,底层已 bonded 1.5s,但应用层 receiver 还没收到 BONDED 广播。若 Cancel 只调 cancelBondProcess(为撤销未完成配对设计)而不查 getBondState(),就空转——日志 btm_confirm_req_reply() State: IDLE Res: 11 / Unexpected pairing confirm。State: IDLE 说明协议栈已无进行中配对,cancelBondProcess 是 no-op。
2.4 BondStateMachine 状态图(含 SDP 等待窗口)
stateDiagram-v2 [*] --> StableState_NONE StableState_NONE --> PendingCommand: createBond() PendingCommand --> PendingCommand: 配对中 SSP/PIN/JustWorks PendingCommand --> WaitSDP_UUID: 底层 BONDED 但 uuids==null state WaitSDP_UUID { [*] --> 等待窗口: 底层已bonded 应用层广播未发 ≤3s } WaitSDP_UUID --> StableState_BONDED: UUID_UPDATE到(:198) 或超时3s(:193) WaitSDP_UUID --> StableState_BONDED: 取消点击 cancelBondProcess ⚠️空转 Res:11 StableState_BONDED --> StableState_NONE: removeBond() note right of WaitSDP_UUID: 2956 的 1.674s 窗口在此!br/getBondState()此时已=BONDEDbr/但 ACTION_BOND_STATE_CHANGED 还没发
3. 日志 tag 速查表(排障核心)
| tag | 所属层 | 含义 | 出什么问题看它 |
|---|---|---|---|
BluetoothDevice/BluetoothAdapter | ① App(SDK) | 公开 API 客户端日志 | cancelBondProcess() for device...(:2465) |
BluetoothManagerService | ② system_server | enable/disable、bind BluetoothApp、发 ACTION_STATE_CHANGED(:4525) | 蓝牙开关打不开 |
BluetoothAdapterService | ③ Bluetooth App | Adapter 主服务,binder 入口,profile 注册(AdapterService.java:202) | enableNative/disableNative/profile 启停 |
BluetoothBondStateMachine | ③ Bluetooth App | 配对状态机,所有 BOND_xxx 广播源头(TAG:72) | is bonded, wait for SDP complete(:583) |
RemoteDevices | ③ Bluetooth App | 远端设备属性表,ACTION_FOUND/ACTION_UUID/ACTION_ACL_* 广播源(RemoteDevices.java:254/1265/1312) | 配对完没 UUID、ACL 异常 |
bt_btif_dm | ④ BTIF(JNI) | Java↔Native 配对/发现桥 | bond_state_changed: new=BONDED 底层状态上抛第一站 |
bt_btm_sec | ④ BTM(安全) | 链路密钥、配对状态机、鉴权 | btm_confirm_req_reply() State: IDLE Res: 11(2956) |
smp_* | ④ SMP | 密钥协商(LE SC/BR-EDR SSP) | smp_save_secure_connections_long_term_key(密钥落盘时刻) |
btsnoop/hci | ⑤ HCI | 芯片层 HCI 包(一般不在 logcat,看 snoop) | 协议级疑难 |
⚠️
btm_confirm_req_reply() State: IDLE Res: 11怎么读:State: IDLE表示当前 BTM 配对状态机空闲(要么没开始,要么已完成 bonded),不是”正在配对中待确认”(新人常误读,刚好相反)。Res:11是拒绝返回码。看到这行 = 你调的cancelBondProcess/setPairingConfirmation在底层 no-op。注:该函数与 Res 值来自历史 bluedroid 实栈,新栈(Gabeldorche/shim)路径不一致,本文仅作日志解读参考。
4. 广播体系:每类广播从哪发、何时到
所有广播在 Bluetooth App 进程发出,经 system_server AMS 转发。转发本身有数十至数百毫秒延迟,叠加 BondStateMachine 主动等待(§2.2)可达秒级。
| Broadcast | 产生源 | 时序保证 |
|---|---|---|
ACTION_STATE_CHANGED | BluetoothManagerService(②):4525 | enable/disable 关键节点,相对即时 |
ACTION_BOND_STATE_CHANGED | BondStateMachine.sendIntent()(:534,分支:605-625) | 被 SDP UUID 等待推迟最多 3s,非即时 |
ACTION_ACL_CONNECTED/DISCONNECTED | RemoteDevices(③):254/1265/1312(数据流 bluedroid→BTIF→AdapterService.JniCallbacks→RemoteDevices.broadcast) | 底层 ACL 建/断时发,贴近底层 |
ACTION_UUID | RemoteDevices(③) SDP 完成回调后 | SDP 完成才有,是解锁 BONDED 广播的触发条件 |
ACTION_PAIRING_REQUEST | BondStateMachine(③):516(入口 sspRequestCallback:659) | 配对请求第一时间发,远早于 BONDED(2956: 43.086 vs 46.534) |
ACTION_FOUND/ACTION_DISCOVERY_* | AdapterService(③) | 扫描期间即时 |
归属纠正(对抗审查):
ACTION_PAIRING_REQUEST由 BondStateMachine(:516) 发出(sspRequestCallback入口 :659),不是 AdapterService;ACTION_ACL_*由 RemoteDevices 发送(AdapterService 同进程但非直接发起方)。
4.1 广播时序图(延迟窗口 vs getBondState 实时)
sequenceDiagram autonumber participant U as 用户 participant App as CarSettings(配对Dialog) participant API as BluetoothDevice.getBondState participant BSM as BondStateMachine participant BTM as btm_sec U->>App: 点击设备 createBond App->>BTM: BTM_CreateBond BTM-->>App: ACTION_BOND_STATE_CHANGED(NONE→BONDING) ★即时(3ms,2956:40.701) Note over BTM,BSM: SSP 协商 ~3s BTM-->>BSM: bond_state=BONDED (44.860) BSM->>BSM: sendIntent: uuids==null<br/>infoLog wait for SDP (:583) BSM--x App: ★不发 BONDED 广播★ sendMessageDelayed(3000ms) Note over App,API: 此时 getBondState() 读 AdapterService 实时态<br/>= BOND_BONDED ←←← 2956修复位点 U->>App: 点取消 (46.336, 落入窗口) App->>BTM: cancelBondProcess ⚠️应改 if bonded→removeBond BTM-->>App: State:IDLE Res:11 (空转) Note over BSM: 1.674s 后 BSM-->>App: ACTION_BOND_STATE_CHANGED(BONDING→BONDED)(46.534 迟到广播) App->>App: dismiss(isPairSuccess=true) ⚠️拉起CarPlay弹窗(2956缺陷二)
5. HCI snoop log
Snoop log 把蓝牙芯片 HCI 层所有命令/事件按 btsnoop 格式抓成 .btsnoop,Wireshark 可看每帧 L2CAP/RFCOMM/ATT/SMP 包。默认位置 /data/misc/bluetooth/logs/。是否开启由 persist.bluetooth.btsnooplogmode 控制(AdapterService.java:1203-1207 读 BluetoothProperties.snoop_log_mode()/Settings.Global.BLUETOOTH_BTSNOOP_DEFAULT_MODE),取值 disabled/filtered/full,出厂默认通常 disabled,bugreport 抓取时自动开启一段时间。
什么场景需要:协议级疑难(SSP 失败看 SMP pairing packet、ACL 异常断看 Disconnection Complete reason、profile 卡住看信令流)。不需要:单纯 App UI bug、广播延迟(看 logcat+bugreport 主 txt 即可,2956 全程没看 snoop)。
6. 对抗自查(新人最易踩的两个误判)
6.1 ❌「BONDED 广播到达 = 配对刚完成」
证伪:2956 实证底层 44.860 已 BONDED、广播 46.534 才到,延迟 1.674s,期间用户已点取消。正确表述:「BONDED 广播到达 = 配对至少在 N 秒前已完成,N 可能 0.05~3+ 秒」。判断”是否刚完成”必须看 BluetoothBondStateMachine tag 时间戳。
6.2 ❌「广播和 getBondState() 二选一,广播更权威」
真相:恰恰相反。getBondState() 读 Bluetooth App 进程(AdapterService)实时状态,广播是它的延迟副本。二者不一致时(BONDED 广播没发、底层已 bonded),getBondState() 才是真相。
证据:2956 root-cause.md §5——“46.336 时 getBondState() 已返回 BONDED(读服务侧状态不依赖广播),if(bondState==BOND_BONDED) removeBond() 技术上可命中”。这正是修复方案(MiBluetoothPairingController.kt:283 onCancel 改 bonded→removeBond)成立的前提。
实战原则:
- 做决策(能不能 removeBond、要不要弹”已配对”)→
getBondState()同步查 - 刷新 UI 列表 → 广播触发(被动通知,最终一致)
- 绝不要用广播到达作为”配对刚完成”时间锚点去关弹窗/启后续(2956 缺陷二就这么中招)
7. 新人下一步
- 拿真实 bugreport(如 2956),按 §3 表逐个 grep tag,对照 §2.4 状态机标每行属哪个状态。
- 重点练识别”广播延迟窗口”:任何
ACTION_BOND_STATE_CHANGED行往回 grepBluetoothBondStateMachine,看 sendIntent 真实时刻。 - 把”广播≠实时 / getBondState 才权威”刻进脑子,能避 80% 蓝牙 UI bug。
附:AOSP 源码核实清单(均 /home/zbc/pangu/yu7/packages/modules/Bluetooth/ 本地核实)
| 论断 | 来源 | 核实 |
|---|---|---|
| BluetoothDevice API 行号 | framework/…/BluetoothDevice.java | ✅ createBond:2299/cancelBondProcess:2457/removeBond:2490/getBondState:2590/setPairingConfirmation:3339 |
| BondStateMachine “wait for SDP” :583 | …/btservice/BondStateMachine.java | ✅ |
| sPendingUuidUpdateTimeoutMillis=3000 :86 / BONDED_INTENT_DELAY=11 :81 | 同上 | ✅ |
| StableState:144 / PendingCommandState:233 / TAG:72 | 同上 | ✅ |
| sendIntent:534(SDP 分支:581-596) | 同上 | ✅ |
| ACTION_PAIRING_REQUEST 发出 :516 / sspRequestCallback:659 | 同上 | ✅(归属 BondStateMachine 非 AdapterService) |
| AdapterService TAG:202 | AdapterService.java | ✅ |
| BluetoothManagerService 在 system_server,:4525 发 ACTION_STATE_CHANGED | service/…/BluetoothManagerService.java | ✅ |
| snoop log 读取 :1203-1207 | AdapterService.java | ✅ |
| ACTION_ACL_* 由 RemoteDevices 发送 | RemoteDevices.java:254/1265/1312 | ✅ |
btm_confirm_req_reply/Res:11 | 历史bluedroid实栈 | ⚠️ 新栈路径不同,仅日志解读参考 |