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流程图:特征读写回调流程
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控制写操作并发,确保每个特征的写入按序执行。这样可以有效避免回调竞争和状态不一致问题。