10 · 蓝牙协议速成(新人第一课)
核心文件:
MiBluetoothUtils.kt(CoD 过滤 :141-170)、ConnectionUtils.kt(CarPlay UUID :30-31) 适用范围:新人第一课,协议地基,不依赖具体业务代码。先建心智模型,再看 01-发现流程。 已核实基准:_已核实事实基准 · 日志实证:MANNPROB-2956 bugreport
0. 一句话心智模型
蓝牙有 三层独立状态——bond(配对/密钥)、ACL(链路)、profile(业务),各自可单独成立或失效。新人 90% 的困惑都来自把这三层混成一层。全篇围绕这句话展开。
1. 两套蓝牙:BR/EDR vs BLE
蓝牙从根上分两套物理传输,并存、不兼容、各有用途:
| 维度 | BR/EDR(经典蓝牙) | BLE(低功耗蓝牙) |
|---|---|---|
| 全称 | Basic Rate / Enhanced Data Rate | Bluetooth Low Energy |
| 物理信道 | 79 个 1MHz 信道 | 40 个信道,信道间距 2MHz(GFSK 占用带宽约 1MHz) |
| 拓扑 | 微微网(1 主 7 从) | 1 主多从 |
| 吞吐 | 高(Mbps 级) | 低(kBps 级) |
| 典型用途 | HFP/PBAP/A2DP(电话/通讯录/音频) | 钥匙、胎压、手环、Beacon |
| 服务发现 | SDP | GATT/ATT |
车机连手机走 BR/EDR(经典蓝牙):手机互联用的 HFP(电话)、PBAP(通讯录)、A2DP(音频)都是经典蓝牙 profile。
⚠️ CarPlay 不是蓝牙 profile。它是 Apple 私有协议,主数据走 WiFi;蓝牙只通过 EIR 中携带的私有 UUID
2d8d2466-...做”设备识别”(ConnectionUtils.kt:30-31的CARPLAY_128UUID,:101-107isCarPlaySupportDevice用getEirDataUuids()匹配)。EIR UUID 不是 SDP 服务 UUID,更不是 profile。详见 12-Profile体系 的”蓝牙跳板”机制。
协议栈分层图(新人先有这张图):
flowchart LR subgraph APP["应用 / profile 层"] HFP[HFP 电话] PBAP[PBAP 通讯录] A2DP[A2DP 音频] GATT[GATT 钥匙/传感器] end subgraph SDP["服务发现层"] SDPX[SDP 经典] GATTX[GATT/ATT BLE] end subgraph L2CAP["L2CAP 数据链路"] end subgraph LINK["链路 / 基带层"] ACL1[ACL 链路 BR/EDR] ACL2[ACL 链路 LE] end subgraph PHY["物理射频"] BR[BR/EDR 79ch] LE[BLE 40ch] end HFP --> SDPX PBAP --> SDPX A2DP --> SDPX GATT --> GATTX SDPX --> L2CAP --> ACL1 --> BR GATTX --> L2CAP --> ACL2 --> LE
日志取证(MANNPROB-2956,车机配对 iPhone):
16:24:40.698 createBond ... transport=0—— 入参transport=0=TRANSPORT_AUTO(App 不指定,交系统选)16:24:44.860 bondStateChangeCallback ... Transport: 1 newState: BOND_BONDED—— 协商结果Transport:1=TRANSPORT_BREDR- 配对后
46.583 HFP(HeadsetClient) 自动连接—— 落定后自动连经典 profile,反证走 BR/EDR
2. 设备怎么被「发现」
flowchart TD A[车机 startDiscovery] --> B[BR/EDR Inquiry 主动扫] A --> C[BLE Scan 被动收广播] D[手机 Advertise 广播自身] --> E[车机收到 RSSI+Name+EIR] B --> E E --> F[ACTION_FOUND 逐设备上报] F --> G[读 Class of Device CoD] G --> H{isDeviceClassTypeSupport?<br/>MiBluetoothUtils:141-170} H -->|true| I[进可用列表] H -->|false| J[丢弃]
- Inquiry:BR/EDR 主动扫描;Scan:BLE 被动监听广播;Advertise:被发现端不停广播自己。
- Android
BluetoothAdapter.startDiscovery()同时做 Inquiry + BLE Scan,默认 12s 后自发ACTION_DISCOVERY_FINISHED。
Class of Device(CoD):24bit 类型码,majorDeviceClass 大类有 PHONE / AUDIO_VIDEO / COMPUTER / WEARABLE / PERIPHERAL 等。
为什么本仓扫描列表只显示手机? 直读 MiBluetoothUtils.isDeviceClassTypeSupport(MiBluetoothUtils.kt:141-170,注意行号以本仓为准):
condition1 = when (majorDeviceClass) {
AUDIO_VIDEO, UNCATEGORIZED -> false
COMPUTER -> deviceClass != COMPUTER_LAPTOP // 排除笔记本
WEARABLE -> deviceClass == WEARABLE_PAGER // 仅寻呼机
else -> true // PHONE 走这里 → true
}
condition2 = when (deviceClass) {
AUDIO_VIDEO_CAR_AUDIO -> false // 屏蔽车机自身
AUDIO_VIDEO_WEARABLE_HEADSET, HEADPHONES -> false // 耳机(死代码:condition1 已 false)
else -> true
}
res = condition1 && condition2
关键结论(已对抗核实,非 bug):因 AUDIO_VIDEO 在 condition1 即 false,所有音视频设备(耳机/音箱/车机自身)都被屏蔽。车机蓝牙列表只显示手机类——这是有意设计:车机蓝牙用于手机互联(电话/通讯录/CarPlay),不连蓝牙耳机听歌。condition2 的耳机分支(:161-164)因 condition1 已 false 而成死代码(防御性冗余)。
3. SDP 与 UUID
SDP(Service Discovery Protocol):经典蓝牙的服务发现协议(BLE 侧对应 GATT/ATT)。配对前后,车机通过 SDP 查询对端”你支持哪些服务(profile)”。
UUID 标识服务:全长 128bit;SIG 预定义 16bit 短码,按规则展开:0000XXXX-0000-1000-8000-00805F9B34FB(XXXX 是 16bit 短码)。
| 16bit 短码 | profile(SIG 公布) |
|---|---|
| 0x1108 | HSP(耳机) |
| 0x110A / 0x110B | A2DP Source / Sink |
| 0x111E / 0x111F | HFP(角色对应见 12-Profile) |
| 0x112E / 0x112F | PBAP PCE(客户端)/ PSE(服务端)(profile 本体=0x1130) |
2d8d2466-e14d-451c-88bc-7301abea291a | CarPlay 私有识别 UUID(非 profile,本仓 ConnectionUtils.kt:30-31) |
ACTION_UUID 何时来?答:SDP 查询完成后。2956 日志 44.862 BondStateMachine: is bonded, wait for service discovery UUIDs 正是此机制——协议栈 44.860 已 BONDED,但应用层 BONDED 广播被 SDP 服务发现推迟约 1.67s 到 46.534 才发(详见 20-Android框架全景)。
4. ACL 连接 vs Profile 连接
- ACL(Asynchronous Connection-Less):BR/EDR 链路层”基础管道”。先有 ACL,profile 才能在上面跑。
ACTION_ACL_CONNECTED/DISCONNECTED:底层 ACL 链路广播,与 profile 无关。- profile 连接:HFP/A2DP/PBAP 各自在已建立的 ACL 之上独立连接。
关系:ACL(管道) ⊃ profile(管道上的业务)。
2956 实证(ACL 与 profile 解耦):
16:24:41.737 btm_acl_created: Created new ACL connection [BT_TRANSPORT_BR_EDR]—— ACL 链路建立16:24:46.583 connectEnabledProfile(HeadsetClientService)—— HFP 发起连接- ACL 比 HFP 连接早约 4.8 秒,证明 ACL 早已存在、profile 连接是后来的事,二者完全解耦。
5. 地址 / 名称 / 别名
| 概念 | 本质 | API | 可变? |
|---|---|---|---|
| 地址(MAC) | 48bit 出厂烧录唯一标识 | getAddress() | 否(出厂固定) |
| 名称(Name) | 对端广播的友好名(EIR/local name) | getName() | 是(对端可改);扫描早期可能 null |
| 别名(Alias) | 本地用户起的备注名,UI 显示时覆盖 Name | getAliasName() / setAlias() | 是(仅本地存储) |
代码里 UI 显示别名优先;日志里 address 是唯一稳定主键(去重、mPendingDevices 按 MAC put)。
6. 三层状态独立(重点图)
逐个证伪常见混淆:
| 混淆 | 证伪 |
|---|---|
| 「配对=连接」 | 错。bond 只交换并落盘密钥,不建 ACL,更不连 profile。2956 中 44.860 BONDED、46.583 才 HFP 连接。 |
| 「发现到=可连接」 | 错。Inquiry 只听见设备在广播,能否连接还看对端是否可连接、信号是否够。 |
| 「bond 完成=能用」 | 错。bond 完只代表有钥匙;要用 HFP 还得 ACL up + HFP profile connect。 |
三层状态图(必须内化)——bond / ACL / profile 各有独立状态机:
stateDiagram-v2 direction LR state "Bond 层(密钥/配对)" as Bond { [*] --> BOND_NONE BOND_NONE --> BOND_BONDING: createBond BOND_BONDING --> BOND_BONDED: SSP成功+密钥落盘 BOND_BONDED --> BOND_NONE: removeBond BOND_BONDING --> BOND_NONE: cancelBondProcess(仅BONDING有效) } state "ACL 层(链路管道)" as ACL { [*] --> ACL_DOWN ACL_DOWN --> ACL_UP: ACTION_ACL_CONNECTED ACL_UP --> ACL_DOWN: ACTION_ACL_DISCONNECTED } state "Profile 层(HFP/PBAP/A2DP)" as Prof { [*] --> DISCONNECTED DISCONNECTED --> CONNECTED: profile.connect(需ACL_UP) CONNECTED --> DISCONNECTED: profile.disconnect / ACL_DOWN } note right of Bond: bond 可在无 ACL 时长期存在<br/>(已配对设备离线仍 remembered) note right of ACL: ACL_UP 通常需 BOND_BONDED note right of Prof: 每个 profile 独立状态<br/>共享同一条 ACL
关于 2956 的 onCancel bug(归因纠正):该 bug 本质是 bond 状态机内部的取消语义错误——
onCancel误把cancelBondProcess(只对 BOND_BONDING 有效)当成能撤销BOND_BONDED的键,协议栈 IDLE 态空转(Res: 11)。它不是 bond/ACL/profile 三层混淆(三层混淆的典型例子是把”ACL_CONNECTED 广播到达=profile 已连接”)。该 bug 的修复方案为onCancel加 bonded→removeBond 降级(commit7239803be在 dev_a17_0610 为悬空提交、当前未合入,bug 仍在),详见 11-配对与SSP。
7. 一页速查卡
| 概念 | 层 | 关键广播/API | 本仓锚点 |
|---|---|---|---|
| Inquiry/Scan | 发现 | startDiscovery() / ACTION_FOUND | MiBluetoothUtils.kt:234-246 |
| CoD 过滤 | 发现 | isDeviceClassTypeSupport | MiBluetoothUtils.kt:141-170 |
| SDP/UUID | 服务发现 | ACTION_UUID(SDP 完成后) | 2956: 44.862 wait for service discovery |
| Bond | 配对 | BOND_BONDED / removeBond | BOND_NONE=10/BONDING=11/BONDED=12 |
| ACL | 链路 | ACTION_ACL_CONNECTED | 2956: 41.737 ACL created |
| Profile | 业务 | HFP/PBAP 各自 connect | 2956: 46.583 HFP 连接 |
| Address/Name/Alias | 标识 | getAddress/getName/setAlias | 列表按 MAC 去重 |
记住三件事:① 车机连手机=BR/EDR;② 扫描列表只显手机=CoD 过滤有意为之;③ bond/ACL/profile 三层独立,取消已配对设备必须 removeBond。