用户可借助此项目在 Flutter 应用中实现蓝牙低功耗(BLE)中心角色功能。它支持跨平台使用,提供扫描、连接设备及读写特征值等核心功能,无额外依赖,且注重错误处理与稳定性。【此简介由AI生成】
注意:此插件是 FlutterBlue 的持续工作成果。
从 FlutterBlue 迁移?请参阅 迁移指南
目录
简介
FlutterBluePlus 是一个适用于 Flutter 的蓝牙低功耗插件。
它只支持 BLE 中央角色(最常见)。
如果您需要 BLE 边缘角色,您应该查看 FlutterBlePeripheral。
教程
如果您是蓝牙初学者,建议先阅读 BLE 教程。
❗ 不支持蓝牙经典 ❗
即 Arduino HC-05 和 HC-06、扬声器、耳机、鼠标、键盘、游戏手柄等均 不支持。它们都使用蓝牙经典。
此外,iBeacons 在 iOS 上 不支持。苹果要求您使用 CoreLocation。
跨平台蓝牙低功耗
FlutterBluePlus 在所有支持的平台(iOS、macOS、Android)上支持几乎所有功能。
FlutterBluePlus 被设计为简单、健壮且易于理解。
无依赖项
FlutterBluePlus 除了 Flutter、Android、iOS 和 macOS 本身外,没有其他依赖项。
这使得 FlutterBluePlus 非常稳定,易于维护。
⭐ 星标 ⭐
请在 GitHub 和 pub.dev 上为这个仓库添加星标。我们都能从拥有一个更大的社区中受益。
Discord 💬
有一个社区 Discord 服务器。(链接)
示例
FlutterBluePlus 提供了一个漂亮的示例应用程序,有助于调试问题。
cd ./example
flutter run
<p align="center">
<img alt="FlutterBlue" src="https://github.com/boskokg/flutter_blue_plus/blob/master/site/example.png?raw=true" />
</p>
## 使用说明
### 🔥 错误处理 🔥
Flutter Blue Plus 对错误处理非常重视。
每个由原生平台返回的错误都会经过检查,并在适当的情况下抛出异常。有关可抛出函数的列表,请参见[参考文献](#reference)。
**流:** 由 FlutterBluePlus 返回的流不会发出任何错误,也永远不会关闭。对于 `stream.listen(...)`,无需处理 `onError` 或 `onDone`。唯一的例外是 `FlutterBluePlus.scanResults`,你需要为其处理 `onError`。
---
### 设置日志级别
// if your terminal doesn't support color you'll see annoying logs like `\x1B[1;35m`
FlutterBluePlus.setLogLevel(LogLevel.verbose, color:false)
设置 LogLevel.verbose 可以显示 所有 的输入和输出数据。
⚫ = 函数名称
🟣 = 传递给平台参数
🟡 = 从平台获取的数据
蓝牙开启与关闭
注意: 在 iOS 上,首次调用 FlutterBluePlus 的任何方法时,系统会弹出一个 "此应用想要使用蓝牙" 的对话框。
// first, check if bluetooth is supported by your hardware
// Note: The platform is initialized on the first call to any FlutterBluePlus method.
if (await FlutterBluePlus.isSupported == false) {
print("Bluetooth not supported by this device");
return;
}
// handle bluetooth on & off
// note: for iOS the initial state is typically BluetoothAdapterState.unknown
// note: if you have permissions issues you will get stuck at BluetoothAdapterState.unauthorized
var subscription = FlutterBluePlus.adapterState.listen((BluetoothAdapterState state) {
print(state);
if (state == BluetoothAdapterState.on) {
// usually start scanning, connecting, etc
} else {
// show an error to the user, etc
}
});
// turn on bluetooth ourself if we can
// for iOS, the user controls bluetooth enable/disable
if (Platform.isAndroid) {
await FlutterBluePlus.turnOn();
}
// cancel to prevent duplicate listeners
subscription.cancel();
扫描设备
如果未找到您的设备,请查看常见问题。
注意: 建议设置扫描过滤器以减少主线程和平台通道的使用。
// listen to scan results
// Note: `onScanResults` only returns live scan results, i.e. during scanning. Use
// `scanResults` if you want live scan results *or* the results from a previous scan.
var subscription = FlutterBluePlus.onScanResults.listen((results) {
if (results.isNotEmpty) {
ScanResult r = results.last; // the most recently found device
print('${r.device.remoteId}: "${r.advertisementData.advName}" found!');
}
},
onError: (e) => print(e),
);
// cleanup: cancel subscription when scanning stops
FlutterBluePlus.cancelWhenScanComplete(subscription);
// Wait for Bluetooth enabled & permission granted
// In your real app you should use `FlutterBluePlus.adapterState.listen` to handle all states
await FlutterBluePlus.adapterState.where((val) => val == BluetoothAdapterState.on).first;
// Start scanning w/ timeout
// Optional: use `stopScan()` as an alternative to timeout
await FlutterBluePlus.startScan(
withServices:[Guid("180D")], // match any of the specified services
withNames:["Bluno"], // *or* any of the specified names
timeout: Duration(seconds:15));
// wait for scanning to stop
await FlutterBluePlus.isScanning.where((val) => val == false).first;
连接至设备
// listen for disconnection
var subscription = device.connectionState.listen((BluetoothConnectionState state) async {
if (state == BluetoothConnectionState.disconnected) {
// 1. typically, start a periodic timer that tries to
// reconnect, or just call connect() again right now
// 2. you must always re-discover services after disconnection!
print("${device.disconnectReasonCode} ${device.disconnectReasonDescription}");
}
});
// cleanup: cancel subscription when disconnected
// - [delayed] This option is only meant for `connectionState` subscriptions.
// When `true`, we cancel after a small delay. This ensures the `connectionState`
// listener receives the `disconnected` event.
// - [next] if true, the the stream will be canceled only on the *next* disconnection,
// not the current disconnection. This is useful if you setup your subscriptions
// before you connect.
device.cancelWhenDisconnected(subscription, delayed:true, next:true);
// Connect to the device
await device.connect();
// Disconnect from device
await device.disconnect();
// cancel to prevent duplicate listeners
subscription.cancel();
自动连接
一旦检测到您的设备,即会自动进行连接。
// enable auto connect
// - note: autoConnect is incompatible with mtu argument, so you must call requestMtu yourself
await device.connect(autoConnect:true, mtu:null)
// wait until connection
// - when using autoConnect, connect() always returns immediately, so we must
// explicity listen to `device.connectionState` to know when connection occurs
await device.connectionState.where((val) => val == BluetoothConnectionState.connected).first;
// disable auto connect
await device.disconnect()
MTU
在Android系统中,我们默认在建立连接时请求一个MTU值为512(详见:connect函数参数)。
在iOS和macOS系统中,MTU值会自动协商确定,一般在135到255之间。
final subscription = device.mtu.listen((int mtu) {
// iOS: initial value is always 23, but iOS will quickly negotiate a higher value
print("mtu $mtu");
});
// cleanup: cancel subscription when disconnected
device.cancelWhenDisconnected(subscription);
// You can also manually change the mtu yourself.
if (Platform.isAndroid) {
await device.requestMtu(512);
}
发现服务
// Note: You must call discoverServices after every re-connection!
List<BluetoothService> services = await device.discoverServices();
services.forEach((service) {
// do something with service
});
读取特性
// Reads all characteristics
var characteristics = service.characteristics;
for(BluetoothCharacteristic c in characteristics) {
if (c.properties.read) {
List<int> value = await c.read();
print(value);
}
}
特性撰写
// Writes to a characteristic
await c.write([0x12, 0x34]);
允许长写入:为了在不受 MTU 限制的情况下写入较大的特性(最多 512 字节),请使用 allowLongWrite。
/// allowLongWrite should be used with caution.
/// 1. it can only be used *with* response to avoid data loss
/// 2. the peripheral device must support the 'long write' ble protocol.
/// 3. Interrupted transfers can leave the characteristic in a partially written state
/// 4. If the mtu is small, it is very very slow.
await c.write(data, allowLongWrite:true);
splitWrite:若需写入大量数据(无限制),您可以定义 splitWrite 函数。
import 'dart:math';
// split write should be used with caution.
// 1. due to splitting, `characteristic.read()` will return partial data.
// 2. it can only be used *with* response to avoid data loss
// 3. The characteristic must be designed to support split data
extension splitWrite on BluetoothCharacteristic {
Future<void> splitWrite(List<int> value, {int timeout = 15}) async {
int chunk = device.mtuNow - 3; // 3 bytes ble overhead
for (int i = 0; i < value.length; i += chunk) {
List<int> subvalue = value.sublist(i, min(i + chunk, value.length));
await write(subvalue, withoutResponse:false, timeout: timeout);
}
}
}
订阅特性
如果从未调用 onValueReceived,请参阅 README 中的 常见问题。
final subscription = characteristic.onValueReceived.listen((value) {
// onValueReceived is updated:
// - anytime read() is called
// - anytime a notification arrives (if subscribed)
});
// cleanup: cancel subscription when disconnected
device.cancelWhenDisconnected(subscription);
// subscribe
// Note: If a characteristic supports both **notifications** and **indications**,
// it will default to **notifications**. This matches how CoreBluetooth works on iOS.
await characteristic.setNotifyValue(true);
最后值流
lastValueStream 是 onValueReceived 的另一种选择。每当特性发生变化时,它都会发射一个值,包括写入操作。
这对于支持写入和读取(及/或通知)的简单特性来说非常方便。例如,一个“灯光开关切换”特性。
final subscription = characteristic.lastValueStream.listen((value) {
// lastValueStream` is updated:
// - anytime read() is called
// - anytime write() is called
// - anytime a notification arrives (if subscribed)
// - also when first listened to, it re-emits the last value for convenience.
});
// cleanup: cancel subscription when disconnected
device.cancelWhenDisconnected(subscription);
// enable notifications
await characteristic.setNotifyValue(true);
读写描述符
// Reads all descriptors
var descriptors = characteristic.descriptors;
for(BluetoothDescriptor d in descriptors) {
List<int> value = await d.read();
print(value);
}
// Writes to a descriptor
await d.write([0x12, 0x34])
服务变更特性监听
FlutterBluePlus会自动监听服务变更特性(0x2A05)。
在FlutterBluePlus中,我们将其称为onServicesReset,因为您必须重新发现服务。
// - uses the GAP Services Changed characteristic (0x2A05)
// - you must call discoverServices() again
device.onServicesReset.listen(() async {
print("Services Reset");
await device.discoverServices();
});
保存设备
要保存一个设备,只需将 remoteId 写在某个地方。
// connect without scanning
final File file = File('/remoteId.txt');
var device = BluetoothDevice.fromId(await file.readAsString());
await device.connect();
获取已连接设备
获取当前连接到您的应用程序的设备。
List<BluetoothDevice> devs = FlutterBluePlus.connectedDevices;
for (var d in devs) {
print(d);
}
获取系统设备
获取连接到系统的由 任何 应用使用的设备。
注意: 在与它们通信之前,你必须将 你的应用 连接到这些设备。
List<BluetoothDevice> devs = await FlutterBluePlus.systemDevices;
for (var d in devs) {
await d.connect(); // Must connect *our* app to the device
await d.discoverServices();
}
建立连接(仅限 Android)
注意: 通常无需调用此功能!!平台会自动执行。
然而,您可以强制弹出窗口更早显示。
final bsSubscription = device.bondState.listen((value) {
print("$value prev:{$device.prevBondState}");
});
// cleanup: cancel subscription when disconnected
device.cancelWhenDisconnected(bsSubscription);
// Force the bonding popup to show now (Android Only)
await device.createBond();
// remove bond
await device.removeBond();
事件API
同步访问所有设备的数据流。
以下为可用的数据流:
- events.onConnectionStateChanged
- events.onMtuChanged
- events.onReadRssi
- events.onServicesReset
- events.onDiscoveredServices
- events.onCharacteristicReceived
- events.onCharacteristicWritten
- events.onDescriptorRead
- events.onDescriptorWritten
- events.onNameChanged(仅限iOS)
- events.onBondStateChanged(仅限Android)
// listen to *any device* connection state changes
FlutterBluePlus.events.onConnectionStateChanged.listen((event)) {
print('${event.device} ${event.connectionState}');
}
模拟
为了在开发过程中模拟 FlutterBluePlus,请参考模拟指南。
快速入门
修改 Android 的 minSdkVersion
flutter_blue_plus 仅兼容 Android SDK 版本 21 及以上,因此您需要在 android/app/build.gradle 中进行相应的修改:
android {
defaultConfig {
minSdkVersion: 21
为 Android 添加权限(无需位置信息)
在 android/app/src/main/AndroidManifest.xml 文件中添加:
<!-- Tell Google Play Store that your app uses Bluetooth LE
Set android:required="true" if bluetooth is necessary -->
<uses-feature android:name="android.hardware.bluetooth_le" android:required="false" />
<!-- New Bluetooth permissions in Android 12
https://developer.android.com/about/versions/12/features/bluetooth-permissions -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- legacy for Android 11 or lower -->
<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" android:maxSdkVersion="30"/>
<!-- legacy for Android 9 or lower -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" android:maxSdkVersion="28" />
为 Android 添加权限(精确位置)
若您想使用蓝牙来确定位置,或支持 iBeacons。
在 android/app/src/main/AndroidManifest.xml 文件中添加:
<!-- Tell Google Play Store that your app uses Bluetooth LE
Set android:required="true" if bluetooth is necessary -->
<uses-feature android:name="android.hardware.bluetooth_le" android:required="false" />
<!-- New Bluetooth permissions in Android 12
https://developer.android.com/about/versions/12/features/bluetooth-permissions -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- legacy for Android 11 or lower -->
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<!-- legacy for Android 9 or lower -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" android:maxSdkVersion="28" />
并在扫描时设置 androidUsesFineLocation:
// Start scanning
flutterBlue.startScan(timeout: Duration(seconds: 4), androidUsesFineLocation: true);
Android Proguard 配置指南
在您的 project/android/app/proguard-rules.pro 文件中添加以下行:
-keep class com.lib.flutter_blue_plus.* { *; }
为避免在您的 release 构建中出现以下类型的错误:
PlatformException(startScan, Field androidScanMode_ for m0.e0 not found. Known fields are
[private int m0.e0.q, private b3.b0$i m0.e0.r, private boolean m0.e0.s, private static final m0.e0 m0.e0.t,
private static volatile b3.a1 m0.e0.u], java.lang.RuntimeException: Field androidScanMode_ for m0.e0 not found
为iOS添加权限
在 ios/Runner/Info.plist 文件中,让我们添加如下内容:
<dict>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app needs Bluetooth to function</string>
关于 iOS 上的位置权限,请查看更多内容:https://developer.apple.com/documentation/corelocation/requesting_authorization_for_location_services
为 macOS 添加权限
确保您已授予对蓝牙硬件的访问权限:
Xcode -> 运行器 -> 目标 -> 运行器 -> 签名与功能 -> 应用程序沙盒 -> 硬件 -> 启用蓝牙
在应用后台使用 BLE
这是一个高级用例。FlutterBluePlus 并不支持所有功能。您可能需要进行分支修改。欢迎提出 PR。
iOS
将以下内容添加到您的 Info.plist 中
<key>UIBackgroundModes</key>
<array>
<string>bluetooth-central</string>
</array>
当在应用的 Info.plist 文件中包含这个键值对时,系统将唤醒应用来处理 BLE 读取、写入和订阅事件。
您可能还需要使用 https://pub.dev/packages/flutter_isolate
注意:应用在被唤醒后,大约有10秒的时间来完成一个任务。后台执行时间过长的应用可能会被系统限制或终止。
Android
您可以尝试使用 https://pub.dev/packages/flutter_foreground_task 或可能使用 https://pub.dev/packages/flutter_isolate
参考文档
🌀 = 流 ⚡ = 同步
FlutterBluePlus API
| Android | iOS | 抛出异常 | 描述 | |
|---|---|---|---|---|
| setLogLevel | ✅ | ✅ | 配置插件日志级别 | |
| setOptions | ✅ | ✅ | 设置可配置的蓝牙选项 | |
| isSupported | ✅ | ✅ | 检查设备是否支持蓝牙 | |
| turnOn | ✅ | 🔥 | 打开蓝牙适配器 | |
| adapterStateNow ⚡ | ✅ | ✅ | 蓝牙适配器的当前状态 | |
| adapterState 🌀 | ✅ | ✅ | 蓝牙适配器的开关状态流 | |
| startScan | ✅ | ✅ | 🔥 | 开始扫描 BLE 设备 |
| stopScan | ✅ | ✅ | 🔥 | 停止当前的 BLE 设备扫描 |
| onScanResults 🌀 | ✅ | ✅ | 实时扫描结果流 | |
| scanResults 🌀 | ✅ | ✅ | 实时扫描结果或之前结果的流 | |
| lastScanResults ⚡ | ✅ | ✅ | 最新的扫描结果 | |
| isScanning 🌀 | ✅ | ✅ | 当前扫描状态的流 | |
| isScanningNow ⚡ | ✅ | ✅ | 当前是否有扫描在运行? | |
| connectedDevices ⚡ | ✅ | ✅ | 连接到 你的应用 的设备列表 | |
| systemDevices | ✅ | ✅ | 🔥 | 连接到系统的设备列表,即使是通过其他应用 |
| getPhySupport | ✅ | 🔥 | 获取支持的蓝牙物理编解码选项 |
FlutterBluePlus 事件 API
| Android | iOS | 抛出异常 | 描述 | |
|---|---|---|---|---|
| events.onConnectionStateChanged 🌀 | ✅ | ✅ | 所有设备的连接状态变化的流 | |
| events.onMtuChanged 🌀 | ✅ | ✅ | 所有设备的 MTU 变化的流 | |
| events.onReadRssi 🌀 | ✅ | ✅ | 所有设备的 RSSI 读取的流 | |
| events.onServicesReset 🌀 | ✅ | ✅ | 所有设备的服务重置的流 | |
| events.onDiscoveredServices 🌀 | ✅ | ✅ | 所有设备发现服务的流 | |
| events.onCharacteristicReceived 🌀 | ✅ | ✅ | 所有设备的特征值读取的流 | |
| events.onCharacteristicWritten 🌀 | ✅ | ✅ | 所有设备的特征值写入的流 | |
| events.onDescriptorRead 🌀 | ✅ | ✅ | 所有设备的描述符值读取的流 | |
| events.onDescriptorWritten 🌀 | ✅ | ✅ | 所有设备的描述符值写入的流 | |
| events.onBondStateChanged 🌀 | ✅ | 所有设备的 Android 键合状态变化的流 | ||
| events.onNameChanged 🌀 | ✅ | 所有设备的 iOS 名称变化的流 |
BluetoothDevice API
| Android | iOS | 抛出异常 | 描述 | |
|---|---|---|---|---|
| platformName ⚡ | ✅ | ✅ | 设备的平台偏好名称 | |
| advName ⚡ | ✅ | ✅ | 扫描期间发现的设备广告名称 | |
| connect | ✅ | ✅ | 🔥 | 与设备建立连接 |
| disconnect | ✅ | ✅ | 🔥 | 取消与设备的活跃或挂起连接 |
| isConnected ⚡ | ✅ | ✅ | 这个设备当前是否连接到 你的应用? | |
| isDisonnected ⚡ | ✅ | ✅ | 这个设备当前是否从 你的应用 断开连接? | |
| connectionState 🌀 | ✅ | ✅ | 蓝牙设备的连接状态流 | |
| discoverServices | ✅ | ✅ | 🔥 | 发现服务 |
| servicesList ⚡ | ✅ | ✅ | 可用服务的当前列表 | |
| onServicesReset 🌀 | ✅ | ✅ | 服务已更改,必须重新发现 | |
| mtu 🌀 | ✅ | ✅ | 当前 MTU 值及变化流 | |
| mtuNow ⚡ | ✅ | ✅ | 当前 MTU 值 | |
| readRssi | ✅ | ✅ | 🔥 | 从连接的设备读取 RSSI |
| requestMtu | ✅ | 🔥 | 请求更改设备的 MTU | |
| requestConnectionPriority | ✅ | 🔥 | 请求更新高优先级、低延迟的连接 | |
| bondState 🌀 | ✅ | 设备键合状态的流。在 Android 上很有用 | ||
| createBond | ✅ | 🔥 | 强制显示系统配对对话框(如果需要) | |
| removeBond | ✅ | 🔥 | 移除设备的蓝牙键合 | |
| setPreferredPhy | ✅ | 🔥 | 为连接和物理选项设置首选的 RX 和 TX 物理 | |
| clearGattCache | ✅ | 🔥 | 清除 android 的服务发现结果缓存 |
BluetoothCharacteristic API
| Android | iOS | Throws | Description | |
|---|---|---|---|---|
| uuid ⚡ | ✅ | ✅ | 特性的UUID | |
| read | ✅ | ✅ | 🔥 | 获取特性值 |
| write | ✅ | ✅ | 🔥 | 写入特性值 |
| setNotifyValue | ✅ | ✅ | 🔥 | 设置特性的通知或指示 |
| isNotifying ⚡ | ✅ | ✅ | 当前是否启用了通知或指示 | |
| onValueReceived 🌀 | ✅ | ✅ | 从设备接收的特性值更新流 | |
| lastValue ⚡ | ✅ | ✅ | 特性的最新值 | |
| lastValueStream 🌀 | ✅ | ✅ | onValueReceived + 写入的值流 |
BluetoothDescriptor API
| Android | iOS | Throws | Description | |
|---|---|---|---|---|
| uuid ⚡ | ✅ | ✅ | 描述符的UUID | |
| read | ✅ | ✅ | 🔥 | 获取描述符的值 |
| write | ✅ | ✅ | 🔥 | 写入描述符的值 |
| onValueReceived 🌀 | ✅ | ✅ | 描述符值读取和写入的流 | |
| lastValue ⚡ | ✅ | ✅ | 描述符的最新值 | |
| lastValueStream 🌀 | ✅ | ✅ | onValueReceived + 写入的值流 |
调试
在FlutterBluePlus中调试问题的最简单方法是创建你自己的本地副本。
cd /user/downloads
git clone https://github.com/boskokg/flutter_blue_plus.git
然后在 pubspec.yaml 文件中通过路径添加仓库:
flutter_blue_plus:
path: /user/downloads/flutter_blue_plus
现在您可以自行编辑 FlutterBluePlus 的代码了。
常见问题
许多常见问题可以轻松解决。
适配器:
扫描:
连接:
读取与写入:
订阅:
- onValueReceived 从不调用(或 lastValueStream)
- onValueReceived 数据被分割(或 lastValueStream)
- onValueReceived 调用时带有重复数据(或 lastValueStream)
Android 错误:
Flutter 错误:
"bluetooth must be turned on"
您需要等待蓝牙适配器完全打开。
await FlutterBluePlus.adapterState.where((state) => state == BluetoothAdapterState.on).first;
您也可以使用 FlutterBluePlus.adapterState.listen(...). 参见使用方法。
adapterState 不是 'on',但我的蓝牙已经开启
对于 iOS:
adapterState 总是初始为 unknown。您需要等待更长的时间让服务初始化。使用以下代码:
// wait for actual adapter state, up to 3 seconds
Set<BluetoothAdapterState> inProgress = {BluetoothAdapterState.unknown, BluetoothAdapterState.turningOn};
var adapterState = FlutterBluePlus.adapterState.where((v) => !inProgress.contains(v)).first;
await adapterState.timeout(const Duration(seconds: 3)).onError((error, stackTrace) {
throw Exception("Could not determine Bluetooth state. ${FlutterBluePlus.adapterStateNow}");
});
// check adapter state
if (FlutterBluePlus.adapterStateNow != BluetoothAdapterState.on) {
throw Exception("Bluetooth Is Not On. ${FlutterBluePlus.adapterStateNow}");
}
如果 adapterState 为 不可用 状态,您必须在应用的 Xcode 设置中添加对蓝牙硬件的访问权限。请参考入门指南。
针对 Android 系统:
请检查您的设备是否支持蓝牙并具有相应的权限。
adapterState 被多次调用
您忘记取消原有的 FlutterBluePlus.adapterState.listen,导致存在多个监听器。
// tip: using ??= makes it easy to only make new listener when currently null
final subscription ??= FlutterBluePlus.adapterState.listen((value) {
// ...
});
// also, make sure you cancel the subscription when done!
subscription.cancel()
扫描无法找到我的设备
1. 你正在使用模拟器
请使用实体设备。
2. 尝试使用其他 BLE 扫描应用
- iOS: nRF Connect
- Android: BLE Scanner
在你的手机上安装一个 BLE 扫描应用。它能找到你的设备吗?
3. 你的设备使用的是经典蓝牙,而非 BLE。
耳机、扬声器、键盘、鼠标、游戏手柄和打印机等都使用经典蓝牙。
这些设备可能在系统设置中可以找到,但无法通过 FlutterBluePlus 连接。FlutterBluePlus 仅支持低功耗蓝牙。
4. 你的设备停止了广播。
- 你可能需要重启你的设备
- 你可能需要将设备置于“发现模式”
- 你的手机可能已经自动连接
- 另一个应用可能已经连接到你的设备
- 另一个手机可能已经连接到你的设备
尝试查看系统设备:
// search system devices. i.e. any device connected to by *any* app
List<BluetoothDevice> system = await FlutterBluePlus.systemDevices;
for (var d in system) {
print('${r.device.platformName} already connected to! ${r.device.remoteId}');
if (d.platformName == "myBleDevice") {
await r.connect(); // must connect our app
}
}
5. 扫描过滤器设置错误。
- 尝试移除所有扫描过滤器
- 要使
withServices功能生效,您的设备必须主动广播它支持的服务UUID
6. Android:您调用 startScan 的频率过高
在 Android 系统中,您只能在每30秒内调用 startScan 五次。这是平台限制。
扫描到的设备永远不会消失
这是预期行为。
如果您希望在设备不再可用时让其消失,您必须设置 removeIfGone 扫描选项。
iBeacons 显示不出来
iOS:
iOS 不支持使用 CoreBluetooth 的 iBeacons。您必须寻找适用于 CoreLocation 的插件。
Android:
- 您需要启用位置权限,请参阅入门指南
- 您必须在
startScan方法中传递androidUsesFineLocation:true。
连接失败
1. 您的蓝牙设备可能电量不足
当您的周边设备电量低时,蓝牙可能会变得不稳定。
2. 您的蓝牙设备可能拒绝了连接或存在故障
连接是一个双向过程。您的蓝牙设备可能配置不正确。
3. 您可能处于蓝牙信号范围的边缘。
信号太弱,或者有大量设备造成无线电干扰。
4. 有些手机在扫描时无法连接。
华为 P8 Lite 是报告存在这一问题的手机之一。尝试在连接前停止您的扫描器。
5. 尝试重启您的手机
蓝牙是一种复杂的服务系统,可能会进入异常状态。
connectionState 被多次调用
您忘记取消最初的 device.connectionState.listen,导致有多个监听器。
// tip: using ??= makes it easy to only make new listener when currently null
final subscription ??= FlutterBluePlus.device.connectionState.listen((value) {
// ...
});
// also, make sure you cancel the subscription when done!
subscription.cancel()
Android 与 iOS & macOS 上的 remoteId 不同
这是预料之中的。无法避免。
出于隐私考虑,iOS & macOS 使用随机生成的 uuid。这个 uuid 会定期变化。
例如:6920a902-ba0e-4a13-a35f-6bc91161c517
Android 使用蓝牙设备的 MAC 地址。它永远不会改变。
例如:05:A4:22:31:F7:ED
iOS: "[错误] 连接意外超时。"
你可以搜索这个错误。这是一个常见的 iOS BLE 错误代码。
这意味着你的设备停止工作了。FlutterBluePlus 无法修复它。
蓝牙 GATT 错误列表
这些 GATT 错误代码是 BLE 规范的一部分。
这些是你的 ble 设备发出的响应,因为你发送了一个无效的请求。
FlutterBluePlus 无法修复这些错误。你做错了什么,你的设备以错误响应。
iOS 上的 GATT 错误显示如下:
apple-code: 1 | The handle is invalid.
apple-code: 2 | Reading is not permitted.
apple-code: 3 | Writing is not permitted.
apple-code: 4 | The command is invalid.
apple-code: 6 | The request is not supported.
apple-code: 7 | The offset is invalid.
apple-code: 8 | Authorization is insufficient.
apple-code: 9 | The prepare queue is full.
apple-code: 10 | The attribute could not be found.
apple-code: 11 | The attribute is not long.
apple-code: 12 | The encryption key size is insufficient.
apple-code: 13 | The value's length is invalid.
apple-code: 14 | Unlikely error.
apple-code: 15 | Encryption is insufficient.
apple-code: 16 | The group type is unsupported.
apple-code: 17 | Resources are insufficient.
apple-code: 18 | Unknown ATT error.
安卓系统中出现的GATT错误:
android-code: 1 | GATT_INVALID_HANDLE
android-code: 2 | GATT_READ_NOT_PERMITTED
android-code: 3 | GATT_WRITE_NOT_PERMITTED
android-code: 4 | GATT_INVALID_PDU
android-code: 5 | GATT_INSUFFICIENT_AUTHENTICATION
android-code: 6 | GATT_REQUEST_NOT_SUPPORTED
android-code: 7 | GATT_INVALID_OFFSET
android-code: 8 | GATT_INSUFFICIENT_AUTHORIZATION
android-code: 9 | GATT_PREPARE_QUEUE_FULL
android-code: 10 | GATT_ATTR_NOT_FOUND
android-code: 11 | GATT_ATTR_NOT_LONG
android-code: 12 | GATT_INSUFFICIENT_KEY_SIZE
android-code: 13 | GATT_INVALID_ATTRIBUTE_LENGTH
android-code: 14 | GATT_UNLIKELY
android-code: 15 | GATT_INSUFFICIENT_ENCRYPTION
android-code: 16 | GATT_UNSUPPORTED_GROUP
android-code: 17 | GATT_INSUFFICIENT_RESOURCES
描述:
1 | Invalid Handle | The attribute handle given was not valid on this server.
2 | Read Not Permitted | The attribute cannot be read.
3 | Write Not Permitted | The attribute cannot be written.
4 | Invalid PDU | The attribute PDU was invalid.
5 | Insufficient Authentication | The attribute requires authentication before it can be read or written.
6 | Request Not Supported | Attribute server does not support the request received from the client.
7 | Invalid Offset | Offset specified was past the end of the attribute.
8 | Insufficient Authorization | The attribute requires an authorization before it can be read or written.
9 | Prepare Queue Full | Too many prepare writes have been queued.
10 | Attribute Not Found | No attribute found within the given attribute handle range.
11 | Attribute Not Long | The attribute cannot be read or written using the Read Blob or Write Blob requests.
12 | Insufficient Key Size | The Encryption Key Size used for encrypting this link is insufficient.
13 | Invalid Attribute Value Length | The attribute value length is invalid for the operation.
14 | Unlikely Error | The request has encountered an unlikely error and cannot be completed.
15 | Insufficient Encryption | The attribute requires encryption before it can be read or written.
16 | Unsupported Group Type | The attribute type is not a supported grouping as defined by a higher layer.
17 | Insufficient Resources | Insufficient Resources to complete the request.
特征写入失败
首先,请查阅蓝牙GATT错误列表以确定错误原因。
1. 蓝牙设备已关闭或超出范围
若设备在写入过程中关闭或崩溃,将导致写入失败。
2. 蓝牙设备存在故障
可能您的设备已崩溃,或由于软件故障未能发送响应。
3. 存在无线干扰
蓝牙为无线技术,可能无法始终正常工作。
特征读取失败
首先,请查阅蓝牙GATT错误列表以确定错误原因。
1. 蓝牙设备已关闭或超出范围
若设备在读取过程中关闭或崩溃,将导致读取失败。
2. 蓝牙设备存在故障
可能您的设备已崩溃,或由于软件故障未能发送响应。
3. 存在无线干扰
蓝牙为无线技术,可能无法始终正常工作。
onValueReceived 从未被调用(或 lastValueStream)
1. 您没有调用正确的函数
lastValueStream 适用于 await chr.read()、await chr.write() 和 await chr.setNotifyValue(true)。
而 onValueReceived 仅适用于 await chr.read() 和 await chr.setNotifyValue(true)。
2. 设备无数据发送
如果您使用 await chr.setNotifyValue(true),则设备决定何时发送数据。
尝试与设备互动以获取新数据。
3. 设备存在故障
尝试重启蓝牙低功耗(BLE)设备。
某些BLE设备可能因软件故障而停止发送数据。
onValueReceived 数据被分割(或 lastValueStream)
请验证mtu是否足够大以容纳您的消息。
device.mtu
若问题依然存在,那可能是您的周边设备出现了问题。
当 onValueReceived 接收到重复数据(或 lastValueStream)时
您可能忘记取消最初的 chr.onValueReceived.listen 监听,导致产生了多个监听器。
最简单的解决方法是使用 device.cancelWhenDisconnected(subscription) 来取消设备的订阅。
final subscription = chr.onValueReceived.listen((value) {
// ...
});
// make sure you have this line!
device.cancelWhenDisconnected(subscription);
await characteristic.setNotifyValue(true);
ANDROID_SPECIFIC_ERROR
没有百分之百的解决方案。
FBP 已经为此错误提供了缓解措施,但 Android 仍会随机地以这个代码失败。
推荐的解决方法是 捕捉 这个错误,然后重试。
android 配对弹窗出现两次
这是 Android 系统本身的一个错误。
你可以在连接后立即自行调用 createBond() 方法,这将解决此问题。
MissingPluginException(No implementation found for method XXXX ...)
如果你刚刚将 flutter_blue_plus 添加到你的 pubspec.yaml 文件中,仅仅进行热重载/热重启是不够的。
你需要完全停止你的应用然后重新运行,以便加载原生插件。
也可以尝试使用 flutter clean。