Flutter BLE蓝牙开发避坑指南:14年硬件APP经验总结
做了14年移动端开发,期间做过扫地机器人、割草机、运动相机的蓝牙连接APP。
BLE这个东西,每次接新项目都能踩到新坑。这篇文章把我遇到过的高频问题和解决方案整理出来,希望能帮到正在做智能硬件配套APP的开发者。
坑1:连接成功但特征值找不到
很多人第一次做BLE,连接成功后直接去读写,结果报错找不到特征值。
原因: 连接成功 ≠ 服务已发现。必须先调用 discoverServices()。
await device.connect();
// ❌ 错误:连接后直接操作
// await characteristic.read();
// ✅ 正确:先发现服务
final services = await device.discoverServices();
for (final service in services) {
for (final char in service.characteristics) {
print('UUID: ${char.uuid}');
}
}
坑2:断线后不会自动重连
BLE连接非常脆弱,手机离设备远一点、切个后台、锁屏,都可能断线。
很多项目只处理了连接,没有处理断线重连,用户体验很差。
正确做法:指数退避重连
// 退避时间:1s → 2s → 5s → 10s → 30s
static const _delays = [
Duration(seconds: 1),
Duration(seconds: 2),
Duration(seconds: 5),
Duration(seconds: 10),
Duration(seconds: 30),
];
void _scheduleReconnect(BluetoothDevice device) {
// 检查是否仍需重连(用户可能已手动断开)
if (!_autoReconnectConfigs.containsKey(device.remoteId.str)) return;
Future.delayed(config.nextDelay(), () async {
try {
await connect(device);
} catch (_) {
// 本次失败,等待下次调度
}
});
}
注意两个细节:
- 重连前要检查用户是否已主动断开,否则会出现用户断开后反复自动重连的问题
- 重连成功后要重置退避计数,避免下次断线等待时间过长
坑3:Android 权限在不同版本行为不一致
Android 12(API 31)之前和之后,蓝牙权限体系完全不同:
<!-- Android 12+ 需要这两个 -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- Android 11 及以下需要这两个 -->
<uses-permission android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<!-- 所有版本都需要位置权限才能扫描 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
代码里统一处理:
static Future<bool> requestAndroidPermissions() async {
final results = await [
Permission.bluetoothScan,
Permission.bluetoothConnect,
Permission.locationWhenInUse,
].request();
return results.values.every((s) => s.isGranted);
}
坑4:收到的数据不知道怎么解析
设备返回的是原始字节 [0x1A, 0x2B, 0x00, 0x64],怎么变成有意义的数值?
// bytes → Int16(小端)
static int toInt16(List<int> bytes, {int offset = 0}) {
final bd = ByteData.sublistView(
Uint8List.fromList(bytes.sublist(offset, offset + 2))
);
return bd.getInt16(0, Endian.little);
}
// bytes → HEX字符串(调试用)
static String toHexString(List<int> bytes) =>
bytes.map((b) => b.toRadixString(16).padLeft(2, '0').toUpperCase())
.join(' ');
// 提取标志位
static bool getBit(int byte, int position) =>
(byte >> position) & 1 == 1;
解析协议帧(大多数硬件设备都有自己的帧格式):
// 帧格式示例:[AA][长度][命令][数据...][XOR校验]
static BLEFrame? parseFrame(List<int> bytes) {
if (bytes.length < 4) return null;
if (bytes[0] != 0xAA) return null; // 帧头校验
final dataLen = bytes[1];
if (bytes.length < dataLen + 4) return null; // 包不完整
final checksum = bytes.sublist(0, 3 + dataLen)
.reduce((a, b) => a ^ b) & 0xFF;
if (checksum != bytes[3 + dataLen]) return null; // 校验失败
return BLEFrame(command: bytes[2], data: bytes.sublist(3, 3 + dataLen));
}
坑5:iOS后台断连
iOS默认会在APP切到后台后断开BLE连接,需要在 Info.plist 声明后台模式:
<key>UIBackgroundModes</key>
<array>
<string>bluetooth-central</string>
</array>
光声明还不够,还要在连接时设置正确的选项,否则锁屏后还是可能断:
await device.connect(
timeout: const Duration(seconds: 35),
autoConnect: false, // 手动控制,不依赖系统autoConnect
);
坑6:同时连多个设备管理混乱
用 Map 统一管理,以 deviceId 为 key:
final Map<String, BluetoothDevice> _connectedDevices = {};
final Map<String, StreamSubscription> _connectionSubs = {};
// 连接时注册
_connectedDevices[device.remoteId.str] = device;
// 断开时清理
_connectedDevices.remove(id);
_connectionSubs[id]?.cancel();
_connectionSubs.remove(id);
Riverpod 状态管理接入
用 StreamProvider 把 BLE 状态和 UI 解耦:
// 扫描结果
final scanResultsProvider = StreamProvider<List<ScanResult>>((ref) {
return ref.watch(bleManagerProvider).scanResults;
});
// 设备连接状态(按设备区分)
final connectionStateProvider = StreamProvider.family<
BluetoothConnectionState, BluetoothDevice>((ref, device) {
return ref.watch(bleManagerProvider).connectionStateOf(device);
});
// UI里使用
ref.watch(scanResultsProvider).when(
data: (results) => ListView(...),
loading: () => CircularProgressIndicator(),
error: (e, _) => Text('$e'),
);
总结
BLE开发的核心难点不在于API调用,而在于:
- 连接状态管理:正确处理断线、重连、多设备
- 权限兼容:iOS和Android两套完全不同的体系
- 数据解析:根据硬件协议正确解析字节流
- 后台保活:平台限制需要额外配置
把以上这些处理好,BLE项目就稳了。
上面这些逻辑我整理成了一套完整的Flutter BLE模板,flutter_blue_plus + Riverpod,可以直接集成进项目:
👉 Flutter BLE完整开发模板(68元)
afdian.com/item/51528c…
包含本文所有代码的完整实现,以及WiFi AP模式配网、完整UI示例。有问题留言。