蓝牙设备扫描与连接:BluetoothAdapter、扫描回调、GATT 连接建立
蓝牙设备发现与连接是离线数据上云流的第一个物理链路。本章从系统 API 层剥解扫描原理、连接握手流程,并给出可复用的接入代码与调试思路。
1. 核心原理与角色分层
| 角色 | 系统组件 | 职责 |
|---|---|---|
| Android OS | BluetoothAdapter | 本地蓝牙适配器代理,提供扫描开关、设备过滤、连接控制 |
| 远端外设 | BluetoothDevice | 代表一个可发现的蓝牙设备,包含 MAC 地址、名称、RSSI |
| 应用层 | BluetoothGatt | GATT 客户端句柄,管理服务发现、MTU 协商、属性读取/通知 |
| 回调 | ScanCallback / GattCallback | 异步事件通知:发现设备、连接状态、服务解析结果 |
2. 扫描流程与 GATT 连接建立步骤
- 检查权限 – 确认
BLUETOOTH_SCAN、BLUETOOTH_CONNECT、ACCESS_FINE_LOCATION已授予。 - 获取适配器 – 通过
BluetoothAdapter.getDefaultAdapter()获得本地适配器实例。 - 启动扫描 – 构造
ScanFilter(按设备名/UUID 过滤),调用bluetoothLeScanner.startScan(filters, settings, callback)。 - 扫描回调 – 在
onScanResult中收到ScanResult,取出BluetoothDevice并记录。 - 停止扫描 – 找到目标设备后立即调用
stopScan(callback),避免电量与射频开销。 - 发起 GATT 连接 – 使用
device.connectGatt(context, autoReconnect, callback)建立连接。 - 服务发现 – GATT 连接成功后,在回调中显式调用
BluetoothGatt.discoverServices()。 - 交互 – 根据业务 UUID 订阅通知(
setCharacteristicNotification)、读写特征值。
关键时序点 – GATT 连接回调
onConnectionStateChange 中 status == BluetoothGatt.GATT_SUCCESS 才表示物理链路建立完成,之后才能调用 discoverServices。
3. 核心 API 与代码骨架
扫描器初始化与过滤
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
BluetoothLeScanner scanner = adapter.getBluetoothLeScanner();
ScanFilter filter = new ScanFilter.Builder()
.setDeviceName("SensorCloud") // 按名称过滤
.build();
ScanSettings settings = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setReportDelay(0) // 实时回调
.build();
扫描回调
private final ScanCallback scanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
BluetoothDevice device = result.getDevice();
int rssi = result.getRssi();
// 匹配目标设备后停止扫描并保存引用
if ("SensorCloud".equals(device.getName())) {
targetDevice = device;
scanner.stopScan(this);
}
}
@Override
public void onScanFailed(int errorCode) {
// 错误码 1 = SCAN_FAILED_ALREADY_STARTED 2 = 应用被限制 3 = 内部错误
}
};
发起 GATT 连接
BluetoothGatt gatt = targetDevice.connectGatt(
context, // 需是 Application 或 Activity context
false, // autoReconnect 通常关闭,手动重连更可控
new MyGattCallback() // 自定义回调实现
);
GATT 回调示例
private class MyGattCallback extends BluetoothGattCallback {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if (status == BluetoothGatt.GATT_SUCCESS) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
gatt.discoverServices(); // 连接成功,开始服务发现
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
// 触发重连逻辑
}
} else {
// status != GATT_SUCCESS,例如 133(GATT_ERROR)或 8(设备断开)
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
// 通过 gatt.getService(UUID) 获取目标服务
}
}
}
4. 调试与常见错误
| 异常表现 | 可能原因 | 解决 |
|---|---|---|
| 扫描无回调 | 缺少位置权限或蓝牙未开启 | 检查运行时权限 + adapter.isEnabled() |
onScanFailed errorCode=2 | 扫描请求被系统限流 | 降低扫描频率,使用 SCAN_MODE_BALANCED,或等待后台周期扫描 |
| GATT 连接返回 status=133 | 远端设备不在广播范围或 GATT 层协议错误 | 增大扫描时间,确保设备在 10 米内;尝试关闭/重开蓝牙 |
| 服务发现后特征值为空 | UUID 拼写错误或设备未公开该服务 | 使用 nRF Connect 工具验证实际 UUID |
| 频繁断连 | 信号弱或 autoReconnect=true 导致重连振荡 | 设为 autoReconnect=false,手动实现指数退避重连 |
Android 版本适配注意 – Android 12+ 要求
BLUETOOTH_SCAN 和 BLUETOOTH_CONNECT 运行时权限,且扫描不可使用隐式 intent;Android 10+ 对后台位置权限有严格限制,前台 Service 是推荐做法。
5. 性能与稳定性优化
- 扫描窗口控制 – 使用
ScanSettings.Builder的setScanMode(SCAN_MODE_LOW_POWER)在日常巡检中降耗,按需切换到 LOW_LATENCY。 - 一次性扫描 – 找到目标设备后立即停止扫描,避免持续广播干扰。
- GATT 重连策略 – 记录 local 和 remote 的 bonding 状态;断连后延迟 1s、2s、4s… 指数退避重连,最多 5 次。
- MTU 协商 – 在
onConnectionStateChange成功后调用gatt.requestMtu(512),提升大数据包传输效率。 - 多设备并发 – 维护
Map<String, BluetoothGatt>,每个设备独立回调线程,避免串行阻塞。
6. 扫描与连接完整流程
调试利器 – 使用 nRF Connect 或 BLE Scanner 快速验证设备广播包、服务 UUID、MTU 限制;对比自己的回调是否符合预期字节。