3. 传统蓝牙 API(BluetoothAdapter)基本调用:设备发现、配对流程、Socket 通信代码
3.1 原理概述
Android 传统蓝牙 API 基于 BluetoothAdapter 单例管理本地蓝牙适配器,通过 BluetoothDevice 表示远程设备,BluetoothSocket 建立 RFCOMM 通信通道。核心流程为:设备发现 → 配对 → 建立连接 → 数据收发。该过程遵循 Bluetooth 2.0+ 的 SDP 和 L2CAP/RFCOMM 协议栈。
| 角色 | 关键类 | 职责 |
|---|---|---|
| 本地适配器 | BluetoothAdapter | 启用/关闭蓝牙、开始/取消发现、获取已配对设备列表 |
| 远程设备 | BluetoothDevice | 封装 MAC 地址、名称、Bonding 状态;发起配对或连接 |
| 通信套接字 | BluetoothSocket | 客户端发起连接,服务端监听;RFCOMM 流式读写 |
| 服务端套接字 | BluetoothServerSocket | 监听特定 UUID 的 RFCOMM 通道,返回 BluetoothSocket |
3.2 设备发现流程
要点:发现过程会持续约 12 秒,需要注册
BroadcastReceiver 监听 ACTION_FOUND 和 ACTION_DISCOVERY_FINISHED 系统广播。- 获取
BluetoothAdapter.getDefaultAdapter(),检查是否支持且已开启。 - 注册
BroadcastReceiver,IntentFilter 包含BluetoothDevice.ACTION_FOUND。 - 调用
mBluetoothAdapter.startDiscovery()(异步)。 - 在广播接收器中通过
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)获取发现的设备。 - 发现结束后(
ACTION_DISCOVERY_FINISHED),取消注册接收器。
// 设备发现核心代码片段
private final BroadcastReceiver discoveryReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
// 添加到列表,记录 device.getName() 和 device.getAddress()
} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
// 更新 UI 或通知扫描结束
}
}
};
// 注册与启动
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
registerReceiver(discoveryReceiver, filter);
mBluetoothAdapter.startDiscovery();
3.3 配对流程(Bonding)
配对在 Android 中通常通过系统对话框自动发起或由程序主动调用 device.createBond() 触发。配对状态变化通过广播 ACTION_BOND_STATE_CHANGED 获取。
- BOND_NONE → 未配对
- BOND_BONDING → 正在配对
- BOND_BONDED → 已配对
// 主动发起配对(需要系统权限 BLUETOOTH_ADMIN)
if (device.getBondState() == BluetoothDevice.BOND_NONE) {
try {
Method createBondMethod = device.getClass().getMethod("createBond");
createBondMethod.invoke(device);
} catch (Exception e) {
e.printStackTrace();
}
}
注意:
createBond() 是隐藏 API,安卓 10+ 可通过 device.createBond() 公开调用,但仍需用户确认 PIN 码。3.4 Socket 通信(RFCOMM)
使用 UUID(标准串口服务 00001101-0000-1000-8000-00805F9B34FB)建立 RFCOMM 通道。服务端监听 → 客户端连接 → 双方通过 InputStream 和 OutputStream 收发数据。
| 步骤 | 服务端(Server) | 客户端(Client) |
|---|---|---|
| 1 | 创建 BluetoothServerSocket,调用 listenUsingRfcommWithServiceRecord(name, uuid) | 获取已配对设备,调用 device.createRfcommSocketToServiceRecord(uuid) |
| 2 | 调用 serverSocket.accept() 阻塞等待连接 | 调用 socket.connect() |
| 3 | 接收成功得到 BluetoothSocket,关闭 ServerSocket | 连接成功后得到活跃 socket |
| 4 | 通过 socket.getInputStream()/getOutputStream() 读写 | 同左 |
// 服务端线程示例
private class AcceptThread extends Thread {
private final BluetoothServerSocket serverSocket;
public AcceptThread(BluetoothAdapter adapter, String name, UUID uuid) {
BluetoothServerSocket tmp = null;
try {
tmp = adapter.listenUsingRfcommWithServiceRecord(name, uuid);
} catch (IOException e) { }
serverSocket = tmp;
}
public void run() {
BluetoothSocket socket = null;
while (true) {
try {
socket = serverSocket.accept();
} catch (IOException e) { break; }
if (socket != null) {
// 在另外线程处理连接
manageConnectedSocket(socket);
break;
}
}
}
}
// 客户端连接示例
public class ConnectThread extends Thread {
private final BluetoothSocket socket;
public ConnectThread(BluetoothDevice device, UUID uuid) {
BluetoothSocket tmp = null;
try {
tmp = device.createRfcommSocketToServiceRecord(uuid);
} catch (IOException e) { }
socket = tmp;
}
public void run() {
mBluetoothAdapter.cancelDiscovery(); // 连接前取消发现
try {
socket.connect();
} catch (IOException e) {
// 处理连接失败
try { socket.close(); } catch (IOException closeEx) { }
return;
}
// 连接成功,开始读写
}
}
3.5 调试与常见错误
- 错误:not discoverable → 确保已开启可检测性(
startActivityForResult请求ACTION_REQUEST_DISCOVERABLE)。 - 错误:Service discovery failed → SDP 查找 UUID 超时;检查 UUID 是否匹配、设备是否在范围内。
- 错误:read failed, socket might closed → 读写线程未处理连接中断;使用心跳机制。
- 错误:Socket 连接一直阻塞 → ServerSocket 未正确关闭或 accept 线程未启动。
调试技巧:使用 adb logcat 过滤
BluetoothAdapter、BluetoothSocket 标签;安卓 9+ 需要 ACCESS_FINE_LOCATION 权限用于扫描。3.6 性能与稳定性优化
- 发现操作非常耗电,建议仅在有用户交互时执行,发现后立即取消。
- 连接前务必
cancelDiscovery(),否则连接会延迟或失败。 - 使用 线程池 管理连接线程,避免频繁创建 Thread。
- 读写使用 循环缓存 或 独立写队列,防止 UI 线程阻塞。
- 增加连接超时(通过
socket.connect()可传入timeout方法?实际上 Android 的connect()没有参数,可使用socket.setSoTimeout()在读写时控制)。