4. 经典蓝牙(BR/EDR)基础:设备发现、配对绑定、SDP与RFCOMM

各位同学,欢迎来到第四章。这一章我们聊聊经典蓝牙,也就是BR/EDR。说实话,很多新手一上来就被各种蓝牙协议搞懵了。我当年也一样,看着一堆英文缩写,头都大了。但别怕,咱们一步步来,把核心流程吃透。

经典蓝牙的核心,说白了就是四个步骤:发现设备 → 配对绑定 → 查询服务 → 建立通信。这就像你认识一个新朋友:先看到人(发现),然后交换名片(配对),接着了解对方会什么(SDP),最后才能愉快地聊天(RFCOMM)。

① 设备发现 Inquiry / Scan ② 配对绑定 Pairing / Bonding ③ 服务发现 SDP ④ 通信 RFCOMM 经典蓝牙(BR/EDR)通信四步曲 每一步都依赖上一步的结果,缺一不可 💡 注意:配对和绑定是两回事,后面会细讲

4.1 设备发现流程

设备发现,就是让蓝牙设备互相“看见”。在经典蓝牙里,这个过程叫 Inquiry(查询)。我刚开始做项目时,以为设备发现就是简单扫一下,结果发现坑不少。

流程其实不复杂:

  • 查询设备(Inquiry):发起方广播查询请求,周围设备响应自己的地址和时钟信息。
  • 可发现模式(Discoverable Mode):被发现的设备必须处于可发现模式,否则“隐身”。
  • 查询扫描(Inquiry Scan):设备周期性监听查询请求,收到后回复。
关键点: 设备发现只获取到蓝牙地址和设备名称,不涉及任何安全信息。你想想看,这就像你在街上看到一个人,只知道他长什么样,还不知道他叫什么、干什么的。

我个人习惯在代码里设置一个 12秒 的发现超时。为什么是12秒?因为蓝牙规范里,一次完整的 Inquiry 周期大约需要 10.24 秒。留点余量,避免因为丢包导致发现不完整。

💡 小技巧: 如果你发现某些设备总是搜不到,先检查它是不是处于“不可发现”模式。我曾经调试一个智能手环,折腾了两小时,最后发现是手环的固件默认关闭了可发现模式。

4.2 配对与绑定机制

找到设备之后,下一步就是配对。这里有个概念必须分清:配对(Pairing)绑定(Bonding) 不是一回事。

概念 说明 是否存储密钥
配对 临时建立安全连接,断开后不保留密钥 ❌ 否
绑定 配对后保存密钥,下次连接自动恢复 ✅ 是

说白了,配对是一次性的,绑定是永久的。你手机连蓝牙耳机,第一次要输入PIN码,以后打开就能连——这就是绑定在起作用。

经典蓝牙的配对方式主要有三种:

  • Legacy Pairing(传统配对):使用PIN码,安全性较低。老设备常用。
  • Secure Simple Pairing(SSP,安全简单配对):蓝牙2.1引入,支持Numeric Comparison、Passkey Entry、Just Works等模式。
  • Secure Connections(安全连接):蓝牙4.2引入,使用ECDH密钥交换,安全性更高。
⚠️ 避坑指南: 我曾经在对接一个老款蓝牙打印机时,发现它只支持 Legacy Pairing。Android 从 4.2 开始默认使用 SSP,导致配对失败。解决方案是手动设置配对模式,或者升级设备固件。所以,做兼容性测试时,一定要拿几台老设备试试。

4.3 服务发现协议(SDP)

配对完成后,你以为就能直接通信了?别急。你还需要知道对方能提供什么服务。这就是 SDP(Service Discovery Protocol) 的活。

SDP 的作用很简单:查询远程设备上有什么服务,以及如何访问这些服务。每个服务都有一个唯一的 UUID 来标识。

举个例子:

  • 串口服务(SPP)的 UUID 是 00001101-0000-1000-8000-00805F9B34FB
  • 文件传输服务的 UUID 是 00001106-0000-1000-8000-00805F9B34FB

在 Android 中,我们通常这样查询服务:

// 获取远程设备的 SDP 记录
BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address);
// 查询 SPP 服务
UUID sppUuid = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
Method method = device.getClass().getMethod("createRfcommSocketToServiceRecord", UUID.class);
BluetoothSocket socket = (BluetoothSocket) method.invoke(device, sppUuid);

嗯,这里要注意:SDP 查询是同步操作,不要在 UI 线程里执行。我习惯用 AsyncTask 或者协程来处理,避免 ANR。

核心思想: SDP 就像是一个“服务目录”。你拿到目录,才知道该调用哪个接口。没有 SDP,你连对方支持什么都不知道,更别提通信了。

4.4 RFCOMM 通信原理

终于到了通信环节。RFCOMM(Radio Frequency Communication) 是经典蓝牙最常用的串口模拟协议。它模拟了 RS-232 串口,让上层应用感觉就像在操作一个串口设备。

RFCOMM 的特点:

  • 基于 L2CAP 协议,提供可靠的数据传输。
  • 支持多路复用,一个物理通道可以承载多个 RFCOMM 会话。
  • 最大数据包长度通常为 1024 字节(但可以协商)。

在 Android 中,RFCOMM 通信的典型流程是:

  1. 客户端调用 createRfcommSocketToServiceRecord() 创建 Socket。
  2. 调用 connect() 发起连接。
  3. 连接成功后,通过 InputStreamOutputStream 读写数据。
  4. 通信结束后,关闭 Socket 释放资源。

我做过一个项目,需要同时传输音频和控制指令。当时我用了两个 RFCOMM 通道:一个传音频流,一个传控制指令。这样互不干扰,调试起来也方便。

💡 经验之谈: RFCOMM 的读写操作是阻塞的。如果你在主线程里读写,界面会卡死。我建议用独立的线程处理 I/O,或者用 HandlerThread。另外,记得处理 IOException,蓝牙连接随时可能断开。

最后,总结一下这一章的核心:

  • 设备发现:找到设备,获取基本信息。
  • 配对绑定:建立安全关系,区分临时和永久。
  • SDP:查询服务,找到正确的通信入口。
  • RFCOMM:模拟串口,实现可靠数据传输。

这四个步骤环环相扣,缺一不可。你想想看,如果跳过 SDP 直接连,你连对方用什么协议都不知道,怎么通信?所以,老老实实按流程来,别偷懒。

好了,这一章就到这里。下一章我们会深入 RFCOMM 的实战编码,包括如何封装一个稳定的蓝牙通信库。到时候见。


公众号:蓝海资料掘金营,微信deep3321