3. 特征读写操作API封装:Android/iOS平台原生BluetoothGatt特征读写接口、回调机制

3.1 原理概述

在BLE(Bluetooth Low Energy)应用中,GATT特征(Characteristic)是数据交换的最小单元。Android和iOS平台均提供原生API实现对特征的读写操作,但两者的回调机制和线程模型存在显著差异。核心原理包括:

  • 读取操作:客户端(Central)向服务端(Peripheral)请求特征值,服务端通过回调返回数据。
  • 写入操作:客户端将数据写入特征,服务端确认接收。写入模式分为带响应(Write with Response)和无响应(Write Without Response)两种。
  • 回调机制:Android使用BluetoothGattCallback接口,iOS使用CBPeripheralDelegate协议。所有操作结果通过异步回调通知应用层。
关键概念:Android的BluetoothGatt.readCharacteristic()和BluetoothGatt.writeCharacteristic()是同步调用方法,但实际结果通过回调返回。iOS的readValue(for:)和writeValue(_:for:type:)则完全是异步设计。

3.2 Android平台API封装与回调实现

3.2.1 核心API与回调接口

Android使用BluetoothGatt类执行读写操作,通过BluetoothGattCallback接收结果。主要方法如下:

方法 描述 回调方法
readCharacteristic(characteristic) 发起特征值读取请求 onCharacteristicRead(gatt, characteristic, value, status)
writeCharacteristic(characteristic) 发起特征值写入请求(带响应) onCharacteristicWrite(gatt, characteristic, status)
writeCharacteristic(characteristic, value, writeType) 发起写入请求,可指定写入模式(API 33+) onCharacteristicWrite(gatt, characteristic, status)

3.2.2 封装示例:特征读取

将特征读取封装为独立方法,并通过回调返回结果:

public void readCharacteristic(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, 
                                final CharacteristicReadCallback callback) {
    if (gatt == null || characteristic == null) {
        callback.onReadFailed("Gatt or characteristic is null");
        return;
    }
    
    boolean success = gatt.readCharacteristic(characteristic);
    if (!success) {
        callback.onReadFailed("readCharacteristic failed immediately");
        return;
    }
    
    // 设置内部回调,在onCharacteristicRead中触发callback
    this.readCallback = callback;
}

private CharacteristicReadCallback readCallback;

// 在BluetoothGattCallback中:
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, 
                                  byte[] value, int status) {
    if (status == BluetoothGatt.GATT_SUCCESS) {
        readCallback.onReadSuccess(value);
    } else {
        readCallback.onReadFailed("GATT status: " + status);
    }
}

3.2.3 封装示例:特征写入

支持带响应和无响应两种写入模式:

public void writeCharacteristic(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, 
                                 byte[] value, int writeType, 
                                 final CharacteristicWriteCallback callback) {
    if (gatt == null || characteristic == null) {
        callback.onWriteFailed("Gatt or characteristic is null");
        return;
    }
    
    // 设置写入值
    characteristic.setValue(value);
    characteristic.setWriteType(writeType); // WRITE_TYPE_DEFAULT 或 WRITE_TYPE_NO_RESPONSE
    
    boolean success = gatt.writeCharacteristic(characteristic);
    if (!success) {
        callback.onWriteFailed("writeCharacteristic failed immediately");
        return;
    }
    
    this.writeCallback = callback;
}

// 在BluetoothGattCallback中:
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, 
                                   int status) {
    if (status == BluetoothGatt.GATT_SUCCESS) {
        writeCallback.onWriteSuccess();
    } else {
        writeCallback.onWriteFailed("GATT status: " + status);
    }
}
常见错误:Android 6.0及以上版本需要在主线程调用readCharacteristic和writeCharacteristic,否则可能导致回调无法触发。推荐在Handler或UI线程执行。

3.3 iOS平台API封装与回调实现

3.3.1 核心API与回调协议

iOS使用CBPeripheral类执行读写操作,通过CBPeripheralDelegate接收结果。主要方法如下:

方法 描述 回调方法
readValue(for: characteristic) 发起特征值读取请求 didUpdateValueFor characteristic:error:
writeValue(data, for: characteristic, type: .withResponse) 写入数据,带响应确认 didWriteValueFor characteristic:error:
writeValue(data, for: characteristic, type: .withoutResponse) 写入数据,无需响应 无回调

3.3.2 封装示例:特征读取

将读取操作封装到Manager类中,并通过闭包回调:

class BLEManager: NSObject, CBPeripheralDelegate {
    private var readCompletion: ((Data?, Error?) -> Void)?
    
    func readCharacteristicFromPeripheral(_ peripheral: CBPeripheral, 
                                           characteristic: CBCharacteristic,
                                           completion: @escaping (Data?, Error?) -> Void) {
        peripheral.delegate = self
        self.readCompletion = completion
        peripheral.readValue(for: characteristic)
    }
    
    // CBPeripheralDelegate
    func peripheral(_ peripheral: CBPeripheral, 
                    didUpdateValueFor characteristic: CBCharacteristic, 
                    error: Error?) {
        guard let completion = readCompletion else { return }
        completion(characteristic.value, error)
        readCompletion = nil // 一次性回调
    }
}

3.3.3 封装示例:特征写入

支持两种写入类型,带响应方式需要处理回调:

func writeCharacteristicToPeripheral(_ peripheral: CBPeripheral,
                                      characteristic: CBCharacteristic,
                                      data: Data,
                                      type: CBCharacteristicWriteType,
                                      completion: ((Error?) -> Void)? = nil) {
    peripheral.delegate = self
    if type == .withResponse {
        self.writeCompletion = completion
        peripheral.writeValue(data, for: characteristic, type: .withResponse)
    } else {
        // 无响应模式,直接写入,无回调
        peripheral.writeValue(data, for: characteristic, type: .withoutResponse)
        completion?(nil)
    }
}

// CBPeripheralDelegate
func peripheral(_ peripheral: CBPeripheral, 
                didWriteValueFor characteristic: CBCharacteristic, 
                error: Error?) {
    guard let completion = writeCompletion else { return }
    completion(error)
    writeCompletion = nil
}
iOS特性:在写入无响应(.withoutResponse)时,CoreBluetooth不会提供任何回调确认。如果需要确认写入成功,必须使用带响应模式,但该模式受MTU限制,单次写入不超过MTU-3字节。

3.4 跨平台API对比

维度 Android iOS
连接角色 Central(GATT Client) Central(CBCentralManager)
核心类 BluetoothGatt + BluetoothGattCallback CBPeripheral + CBPeripheralDelegate
读取回调 onCharacteristicRead(gatt, characteristic, value, status) didUpdateValueFor characteristic:error:
写入回调(带响应) onCharacteristicWrite(gatt, characteristic, status) didWriteValueFor characteristic:error:
无响应写入确认 无法确认 无回调,无法确认
线程模型 需在主线程调用 自动在主队列或指定队列

3.5 内联SVG流程图:特征读写回调流程

特征读写回调流程图(Android / iOS 双平台) 应用程序 BluetoothGatt API BluetoothGattCallback CBPeripheral API CBPeripheralDelegate BLE Controller / 蓝牙控制器 远端设备 GATT 特征 要点 Android: 所有 GATT 操作需在主线程调用,结果通过 BluetoothGattCallback 异步返回。 iOS: 方法全异步,在 CBPeripheralDelegate 回调中获取结果,支持 withResponse / withoutResponse。

3.6 调试与部署注意事项

  • Android调试:启用Bluetooth HCI Snoop Log,使用/sdcard/btsnoop_hci.log捕获原始BLE数据包,结合Wireshark分析读写交互。
  • iOS调试:使用Xcode的Core Bluetooth调试模板,或通过PacketLogger.app捕获蓝牙HCI日志。
  • 特征并发读写:避免同时向同一特征发起多个读写操作,可能导致状态混乱。建议维护操作队列,顺序执行。
  • MTU协商:写入大数据前,先通过negotiateMTU协商最大字节数,避免写入长度超过设备支持范围。

3.7 常见错误与稳定性优化

问题 原因 解决方案
读取回调不触发 Android: 未在主线程调用;iOS: delegate未设置 确保在主线程执行;重新设置peripheral.delegate
写入失败(GATT_ERROR) MTU不足或特征属性不支持写入 检查特征properties;扩大MTU
写入无响应无确认 Android/iOS: .withoutResponse模式无回调 改用.withResponse;或使用自定义确认协议
回调线程竞争 多个操作交错,回调覆盖 使用操作队列+单例模式管理pending回调
推荐实践:在Android端封装统一的读写管理器类,通过HashMap维护请求的唯一ID与回调的映射关系。iOS端使用OperationQueue控制写操作并发,确保每个特征的写入按序执行。这样可以有效避免回调竞争和状态不一致问题。