蓝牙设备扫描与连接:BluetoothAdapter、扫描回调、GATT 连接建立

蓝牙设备发现与连接是离线数据上云流的第一个物理链路。本章从系统 API 层剥解扫描原理、连接握手流程,并给出可复用的接入代码与调试思路。

1. 核心原理与角色分层

角色系统组件职责
Android OSBluetoothAdapter本地蓝牙适配器代理,提供扫描开关、设备过滤、连接控制
远端外设BluetoothDevice代表一个可发现的蓝牙设备,包含 MAC 地址、名称、RSSI
应用层BluetoothGattGATT 客户端句柄,管理服务发现、MTU 协商、属性读取/通知
回调ScanCallback / GattCallback异步事件通知:发现设备、连接状态、服务解析结果

2. 扫描流程与 GATT 连接建立步骤

  1. 检查权限 – 确认 BLUETOOTH_SCAN、BLUETOOTH_CONNECT、ACCESS_FINE_LOCATION 已授予。
  2. 获取适配器 – 通过 BluetoothAdapter.getDefaultAdapter() 获得本地适配器实例。
  3. 启动扫描 – 构造 ScanFilter(按设备名/UUID 过滤),调用 bluetoothLeScanner.startScan(filters, settings, callback)。
  4. 扫描回调 – 在 onScanResult 中收到 ScanResult,取出 BluetoothDevice 并记录。
  5. 停止扫描 – 找到目标设备后立即调用 stopScan(callback),避免电量与射频开销。
  6. 发起 GATT 连接 – 使用 device.connectGatt(context, autoReconnect, callback) 建立连接。
  7. 服务发现 – GATT 连接成功后,在回调中显式调用 BluetoothGatt.discoverServices()。
  8. 交互 – 根据业务 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. 扫描与连接完整流程

蓝牙扫描与 GATT 连接流程 ① 获取适配器 ② 启动 LE 扫描 ③ 扫描回调 ④ 停止扫描 ⑤ 建立 GATT ⑥ 服务发现 发现目标设备 后立即停止扫描
调试利器 – 使用 nRF Connect 或 BLE Scanner 快速验证设备广播包、服务 UUID、MTU 限制;对比自己的回调是否符合预期字节。