4. 传统BLE API基本调用:扫描回调、GATT连接、服务发现

本章带你深入Android传统BLE API(即 BluetoothLeScanner 系列)的基本调用链路。你将掌握从扫描外设到GATT连接、服务发现、特征操作的完整实现,并理解该API在Android 5.0+ 设备上的行为特性。后续章节的向后兼容方案都将以此为基础展开。

4.1 核心调用流程

传统BLE API的核心调用可拆解为以下关键步骤,每一步都依赖特定的回调或方法:

  1. 初始化 BluetoothLeScanner 实例;
  2. 实现 ScanCallback 并调用 startScan();
  3. 在 onScanResult() 中解析外设并确认目标设备;
  4. 通过 BluetoothDevice.connectGatt() 发起GATT连接;
  5. 重写 BluetoothGattCallback.onConnectionStateChange() 监听连接结果;
  6. 连接成功后调用 BluetoothGatt.discoverServices();
  7. 在 onServicesDiscovered() 中获取服务和特征值;
  8. 对特征值进行读写或订阅操作。

4.2 调用流程图

传统BLE调用流程 初始化BluetoothLeScanner 扫描回调 onScanResult 调用 connectGatt() onConnectionStateChange discoverServices() onServicesDiscovered 操作特征值(读写/订阅) 设备未开启BLE或 getBluetoothLeScanner 返回null需判空

4.3 关键API与回调对比

角色 类 / 接口 核心方法/回调 用途
扫描启动器 BluetoothLeScanner startScan() 发起BLE扫描
扫描结果回调 ScanCallback onScanResult() 每发现一个外设调用一次
GATT连接器 BluetoothDevice connectGatt() 建立GATT连接
GATT状态回调 BluetoothGattCallback onConnectionStateChange() 连接/断连通知
服务发现完成 BluetoothGattCallback onServicesDiscovered() 服务列表就绪
特征值操作 BluetoothGatt readCharacteristic()/setCharacteristicNotification() 读写或订阅通知

4.4 代码示例:扫描 → 连接 → 发现服务 → 特征读取

关键前提:确保已声明 BLUETOOTH_SCAN、BLUETOOTH_CONNECT 权限,并在运行时请求 ACCESS_FINE_LOCATION(Android 11 以下)或 BLUETOOTH_CONNECT(Android 12+)。
// 1. 初始化 BluetoothLeScanner
BluetoothManager manager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
BluetoothAdapter adapter = manager.getAdapter();
BluetoothLeScanner scanner = adapter.getBluetoothLeScanner();
if (scanner == null) {
    // 设备不支持 BLE 或蓝牙未开启
    return;
}

// 2. 实现扫描回调
private ScanCallback scanCallback = new ScanCallback() {
    @Override
    public void onScanResult(int callbackType, ScanResult result) {
        BluetoothDevice device = result.getDevice();
        int rssi = result.getRssi();
        if (device.getName() != null && device.getName().startsWith("MyBleDevice")) {
            scanner.stopScan(this);
            // 3. 连接 GATT
            device.connectGatt(context, false, gattCallback);
        }
    }

    @Override
    public void onScanFailed(int errorCode) {
        // 处理扫描失败
    }
};

// 4. 开始扫描
scanner.startScan(scanCallback);

// 5. GATT 回调
private BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
        if (newState == BluetoothProfile.STATE_CONNECTED) {
            gatt.discoverServices();
        } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
            // 处理断连
        }
    }

    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
        if (status == BluetoothGatt.GATT_SUCCESS) {
            BluetoothGattService service = gatt.getService(
                UUID.fromString("0000180f-0000-1000-8000-00805f9b34fb"));
            if (service != null) {
                BluetoothGattCharacteristic characteristic = service.getCharacteristic(
                    UUID.fromString("00002a19-0000-1000-8000-00805f9b34fb"));
                gatt.readCharacteristic(characteristic);
            }
        }
    }

    @Override
    public void onCharacteristicRead(BluetoothGatt gatt,
                                     BluetoothGattCharacteristic characteristic,
                                     int status) {
        if (status == BluetoothGatt.GATT_SUCCESS) {
            byte[] value = characteristic.getValue();
            // 解析数据
        }
    }
};
多个外设同时连接时,需要在 onConnectionStateChange 中根据 BluetoothDevice 对象区分设备。推荐设计一个设备连接状态机来处理并发。

4.5 常见错误与调试

  • 扫描失败:SCAN_FAILED_APPLICATION_REGISTRATION_FAILED(权限未授予)或 SCAN_FAILED_FEATURE_UNSUPPORTED(芯片不支持后台扫描)。
  • 连接失败:onConnectionStateChange 中 status != GATT_SUCCESS,可能原因包括设备已连接、GATT 数据库损坏或超出连接数量限制。
  • 服务发现为空:getService() 返回 null,检查 UUID 是否匹配,或在 onServicesDiscovered 中轮询 getServices() 列表。
  • 无回调触发:未在正确线程调用 connectGatt() 或未持有 BluetoothGatt 强引用,系统可能回收回调对象。
扫描到连接操作之间不要调用 BluetoothLeScanner.stopScan() 以外的同步操作,避免触发系统级扫描锁竞争导致后续 startScan 返回 SCAN_FAILED_ALREADY_STARTED。

4.6 性能与稳定性优化

  • 扫描功耗控制:使用 startScan(List<ScanFilter>, ScanSettings, ScanCallback) 设置合理的扫描间隔和扫描模式(SCAN_MODE_LOW_LATENCY 或 SCAN_MODE_BALANCED)。
  • 连接复用:对同一外设避免重复调用 connectGatt(),先检查 BluetoothDevice.getGatt() 或维护连接状态缓存。
  • 服务发现超时:在 onConnectionStateChange 成功后的500ms延迟内调用 discoverServices(),防止系统 I/O 阻塞导致发现失败。
  • 线程隔离:所有 GATT 操作方法 readCharacteristic、writeCharacteristic 必须在 BluetoothGattCallback 的回调线程中顺序执行。