使用Flutter Blue Plus读取ESP32特征时触发PlatformException问题
Flutter Blue Plus读取ESP32蓝牙特征触发PlatformException异常排查与解决
问题现象
使用Flutter Blue Plus库从ESP32读取蓝牙特征时,触发PlatformException,异常信息如下:
PlatformException(read_characteristic_error,unknown reason,may occur if readCharacteristic was called before last read finished, null, null)
用户提供的读取特征代码:
Future<void> readData async{ BluetoothService? DataService = BLEComm().getService(DeviceServiceUUIDs.Service); if (null != DataService) { await Future.delayed(Duration(milliseconds:50),() async{ BluetoothCharacteristic? DurationCharacteristic = BLEComm().getCharacteristic( DeviceServiceUUIDs.duration, DataService); List<int>? DurationResponse = await BLEComm() .readCharacteristic(DurationCharacteristic); String Duration = String.fromCharCodes(DurationResponse!); var Duration_Data = double.parse(Duration); });
依赖的底层BLEComm实现代码:
/// Get a specific service from the list of services. BluetoothService? getService(String uuid) { for (BluetoothService? service in _deviceServices) { if (service?.uuid.toString() == uuid) { return service; } } return null; } /// Get Characteristic object from a service BluetoothCharacteristic? getCharacteristic( String uuid, BluetoothService bleService) { var characteristics = bleService.characteristics; for (BluetoothCharacteristic characteristic in characteristics) { if (characteristic.uuid.toString() == uuid) { return characteristic; } } return null; } /// read value to a characteristic /// Throws Exceptions on Non readable characteristic and general exceptions. Future<List<int>?> readCharacteristic( BluetoothCharacteristic? characteristic) async { List<int>? value; try { if (null != characteristic && !characteristic.properties.read) { throw ("characteristic ${characteristic.uuid.toString()} is not readable"); } value = await characteristic?.read(); } catch (ex) { throw Exception(ex); } return value; }
排查分析
从异常提示readCharacteristic was called before last read finished可以确定,核心问题是重复发起特征读取请求,前一次读取操作还未完成就触发了新的请求。结合代码细节,具体问题点包括:
- BLEComm实例重复创建:每次调用
BLEComm()都会生成新实例,无法统一管理蓝牙操作状态,容易引发并发读取冲突。 - 无并发控制机制:没有状态标记或锁来限制同一时间仅执行一次读取操作,若
readData被多次调用会直接触发重复请求。 - Future.delayed无效:用固定延迟规避时序问题不可靠,无法精准确保前一次读取操作完成。
- 空值处理不严谨:
DurationResponse!强制解包会掩盖读取失败的错误,同时可能触发空指针异常。
解决方法
1. 改造BLEComm为单例模式
确保全局只有一个BLEComm实例,统一管理蓝牙操作状态:
class BLEComm { static final BLEComm _instance = BLEComm._internal(); factory BLEComm() => _instance; BLEComm._internal(); // 原有getService、getCharacteristic方法保持不变 }
2. 添加读取操作的并发控制
在BLEComm中加入状态标记,避免同一时间执行多个读取操作:
class BLEComm { static final BLEComm _instance = BLEComm._internal(); factory BLEComm() => _instance; BLEComm._internal(); bool _isReading = false; // 读取状态标记 Future<List<int>?> readCharacteristic( BluetoothCharacteristic? characteristic) async { if (_isReading) { throw Exception("前一次读取操作尚未完成"); } List<int>? value; try { _isReading = true; if (null != characteristic && !characteristic.properties.read) { throw ("characteristic ${characteristic.uuid.toString()} 不支持读取"); } value = await characteristic?.read(); } catch (ex) { throw Exception(ex); } finally { _isReading = false; // 无论成败都重置状态 } return value; } // 原有getService、getCharacteristic方法保持不变 }
3. 优化readData方法逻辑
移除无效的延迟,完善空值和异常处理:
Future<void> readData() async { final bleComm = BLEComm(); BluetoothService? dataService = bleComm.getService(DeviceServiceUUIDs.Service); if (dataService == null) { print("未找到目标服务"); return; } BluetoothCharacteristic? durationCharacteristic = bleComm.getCharacteristic( DeviceServiceUUIDs.duration, dataService, ); if (durationCharacteristic == null) { print("未找到目标特征"); return; } try { List<int>? durationResponse = await bleComm.readCharacteristic(durationCharacteristic); if (durationResponse == null || durationResponse.isEmpty) { print("读取结果为空"); return; } String durationStr = String.fromCharCodes(durationResponse); double durationData = double.parse(durationStr); print("读取到的时长数据:$durationData"); // 此处添加数据处理逻辑 } catch (e) { print("读取特征失败:$e"); } }
4. 检查ESP32端蓝牙逻辑
确认ESP32的BLE特征读取处理代码无阻塞,确保每次读取请求都能及时返回响应,避免因硬件端未完成处理导致客户端重复发起请求。
内容的提问来源于stack exchange,提问作者Nns_ninteyFIve
相关产品推荐
相关产品推荐

