源码解析
构造函数
Dart
final DeviceIdentifier remoteId; //mac地址(Android) && UUID(IOS)
BluetoothDevice({ //构造函数
required this.remoteId,
});
//命名构造函数:从Protobuf(协议缓冲区)对象创建一个BluetoothDevice实例
//BmBluetoothDevice是生成的Protobuf类,这个类用于Flutter与原生平台之间的数据传递
BluetoothDevice.fromProto(BmBluetoothDevice p) : remoteId = p.remoteId;
/// Create a device from an id
/// - to connect, this device must have been discovered by your app in a previous scan
//(这个设备必须被你的APP扫描发现过,才能成功连接)
/// - iOS uses 128-bit uuids the remoteId, e.g. e006b3a7-ef7b-4980-a668-1f8005f84383
/// - Android uses 48-bit mac addresses as the remoteId, e.g. 06:E5:28:3B:FD:E0
BluetoothDevice.fromId(String remoteId) : remoteId = DeviceIdentifier(remoteId);
get方法
Dart
/// platform name(平台名称,来自GAP服务)
/// - this name is kept track of by the platform
/// - this name usually persist between app restarts(应用重启后还存在)
/// - iOS: after you connect, iOS uses the GAP name characteristic (0x2A00)
/// if it exists. Otherwise iOS use the advertised name.
/// - Android: always uses the advertised name
String get platformName => FlutterBluePlus._platformNames[remoteId] ?? "";
/// Advertised Named(广播名称,来自广播包)
/// - this is the name advertised by the device during scanning
/// - it is only available after you scan with FlutterBluePlus
/// - it is cleared when the app restarts.(应用重启后丢失)
/// - not all devices advertise a name
String get advName => FlutterBluePlus._advNames[remoteId] ?? "";
// iOS 的特殊行为,iOS 连接后会自动读取 GAP 名称并缓存,即使设备断开,platformName 仍然可用
// Android 的行为,Android 主要依赖广播名称,platformName 通常和 advName 相同,除非手动读取 GAP 服务
/// Get services
/// - returns empty if discoverServices() has not been called
/// or if your device does not have any services (rare)
List<BluetoothService> get servicesList {
BmDiscoverServicesResult? result = FlutterBluePlus._knownServices[remoteId];//找服务
if (result == null) {
return [];
} else {
return result.services.map((p) => BluetoothService.fromProto(p)).toList();
}
}
/// Returns true if autoConnect is currently enabled for this device
bool get isAutoConnectEnabled { //是否开启了自动重连
return FlutterBluePlus._autoConnect.contains(remoteId);
}
/// Returns true if this device is currently connected to your app
bool get isConnected { //是否已经连接
if (FlutterBluePlus._connectionStates[remoteId] == null) {
return false;
} else {
var state = FlutterBluePlus._connectionStates[remoteId]!.connectionState;
return state == BmConnectionStateEnum.connected;
}
}
/// Returns true if this device is currently disconnected from your app
bool get isDisconnected => isConnected == false; //是否已经断连
/// The most recent disconnection reason - 最近一次断开连接的原因
DisconnectReason? get disconnectReason {
if (FlutterBluePlus._connectionStates[remoteId] == null) { //设备是否有连接记录
return null;
}
//从缓存的连接状态中提取断开原因码和描述,封装成DisconnectReason对象返回
int? code = FlutterBluePlus._connectionStates[remoteId]!.disconnectReasonCode;
String? description = FlutterBluePlus._connectionStates[remoteId]!.disconnectReasonString;
return DisconnectReason(_nativeError, code, description);
}
/// The current connection state *of our app* to the device - 连接状态流
Stream<BluetoothConnectionState> get connectionState {
// initial value - Note: we only care about the current connection state of
// *our* app, which is why we can use our cached value, or assume disconnected
//获取缓存的初始值
BluetoothConnectionState initialValue = BluetoothConnectionState.disconnected;
if (FlutterBluePlus._connectionStates[remoteId] != null) {
initialValue = _bmToConnectionState(FlutterBluePlus._connectionStates[remoteId]!.connectionState);
}
//监听原生层的变化事件
return FlutterBluePlus._methodStream.stream
.where((m) => m.method == "OnConnectionStateChanged") //过滤连接状态变化事件
.map((m) => m.arguments) //提取参数
.map((args) => BmConnectionStateResponse.fromMap(args)) //反序列化
.where((p) => p.remoteId == remoteId) //只关注当前设备
.map((p) => _bmToConnectionState(p.connectionState));//转化为Dart枚举
//.newStreamWithInitialValue(initialValue); //一监听连接状态,流会自动发送一个默认状态
}
/// The current MTU size in bytes - 当前MTU值
int get mtuNow {
// get initial value from our cache
return FlutterBluePlus._mtuValues[remoteId]?.mtu ?? 23;
}
/// Stream emits a value:
/// - immediately when first listened to
/// - whenever the mtu changes
Stream<int> get mtu { //MTU变化流
// get initial value from our cache
int initialValue = FlutterBluePlus._mtuValues[remoteId]?.mtu ?? 23; //初始值
return FlutterBluePlus._methodStream.stream
.where((m) => m.method == "OnMtuChanged") //监听MTU变化事件
.map((m) => m.arguments)
.map((args) => BmMtuChangedResponse.fromMap(args))
.where((p) => p.remoteId == remoteId)//只关注当前设备
.map((p) => p.mtu) //提取MTU值
.newStreamWithInitialValue(initialValue);//订阅时立即发送当前值
}
/// Services Reset Stream - 服务重置流
/// - uses the GAP Services Changed characteristic (0x2A05)
/// - you must re-call discoverServices() when services are reset
Stream<void> get onServicesReset {
return FlutterBluePlus._methodStream.stream
.where((m) => m.method == "OnServicesReset") //监听服务重置事件
.map((m) => m.arguments)
.map((args) => BmBluetoothDevice.fromMap(args))
.where((p) => p.remoteId == remoteId) //只关注当前设备
.map((m) => null); //转为void
}
设备断开时自动取消订阅
Dart
/// Register a subscription to be canceled when the device is disconnected.
/// This function simplifies cleanup, to prevent creating duplicate stream subscriptions.
/// - this is an optional convenience function
/// - prevents accidentally creating duplicate subscriptions on each reconnection.
/// - [next] if true, the the stream will be canceled only on the *next* disconnection.
/// This is useful if you setup your subscriptions before you connect.
/// - [delayed] Note: 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.
// 设备断开时自动取消订阅
void cancelWhenDisconnected(StreamSubscription subscription, {bool next = false, bool delayed = false}) {
if (isConnected == false && next == false) {
subscription.cancel(); // cancel immediately if already disconnected.
} else if (delayed) {
FlutterBluePlus._delayedSubscriptions[remoteId] ??= [];
FlutterBluePlus._delayedSubscriptions[remoteId]!.add(subscription);
} else {
FlutterBluePlus._deviceSubscriptions[remoteId] ??= [];
FlutterBluePlus._deviceSubscriptions[remoteId]!.add(subscription);
}
}
连接方法
Dart
/// Establishes a connection to the Bluetooth Device.
/// [timeout] if timeout occurs, cancel the connection request and throw exception
/// [mtu] Android only. Request a larger mtu right after connection, if set.
/// [autoConnect] reconnect whenever the device is found
/// - if true, this function always returns immediately.
/// - you must listen to `connectionState` to know when connection occurs.
/// - auto connect is turned off by calling `disconnect`
/// - auto connect results in a slower connection process compared to a direct connection
/// because it relies on the internal scheduling of background scans.
Future<void> connect({
Duration timeout = const Duration(seconds: 35), //连接超时范围
int? mtu = 512, //Android设置MTU
bool autoConnect = false, //是否自动重连
}) async {
// If you hit this assert, you must set `mtu:null`, i.e `device.connect(mtu:null, autoConnect:true)`
// and you'll have to call `requestMtu` yourself. `autoConnect` is not compatibile with `mtu`.
//参数检查,mtu和autoConnect不能同时使用
assert((mtu == null) || !autoConnect, "mtu and auto connect are incompatible");
// make sure no one else is calling disconnect
//互斥锁保护,防止连接和断连同时执行
_Mutex dmtx = _MutexFactory.getMutexForKey("disconnect");
bool dtook = await dmtx.take();
// Only allow a single ble operation to be underway at a time
//_Mutex mtx = _MutexFactory.getMutexForKey("global");
//await mtx.take();
try {
// remember auto connect value(记录自动重连标记)
if (autoConnect) {
FlutterBluePlus._autoConnect.add(remoteId);
}
var request = BmConnectRequest(//构造请求参数,发生给原生平台数据
remoteId: remoteId,
autoConnect: autoConnect,
);
//设置响应监听器 - 准备监听连接状态变化
//原生平台 → MethodChannel → _methodStream → 过滤出连接状态事件 → 过滤出本设备 → 取第一个
var responseStream = FlutterBluePlus._methodStream.stream
.where((m) => m.method == "OnConnectionStateChanged")
.map((m) => m.arguments)
.map((args) => BmConnectionStateResponse.fromMap(args))
.where((p) => p.remoteId == remoteId);
// Start listening now, before invokeMethod, to ensure we don't miss the response
Future<BmConnectionStateResponse> futureState = responseStream.first;
// invoke(调用原生连接方法)
bool changed = await FlutterBluePlus._invokeMethod('connect', request.toMap());
// we return the disconnect mutex now so that this
// connection attempt can be canceled by calling disconnect
dtook = dmtx.give(); //释放断开锁
// only wait for connection if we weren't already connected
if (changed && !autoConnect) { //处理非自动连接的情况
BmConnectionStateResponse response = await futureState
.fbpEnsureAdapterIsOn("connect") //确保蓝牙已经开启
.fbpTimeout(timeout.inSeconds, "connect") //设置超时
.catchError((e) async {
if (e is FlutterBluePlusException && e.code == FbpErrorCode.timeout.index) {
await FlutterBluePlus._invokeMethod('disconnect', remoteId.str); // cancel connection attempt
}
throw e;
});
// failure?(检查连接失败)
if (response.connectionState == BmConnectionStateEnum.disconnected) {
// if (response.disconnectReasonCode == 23789258) {
// throw FlutterBluePlusException(
// ErrorPlatform.fbp, "connect", FbpErrorCode.connectionCanceled.index, "connection canceled");
// } else {
// throw FlutterBluePlusException(
// _nativeError, "connect", response.disconnectReasonCode, response.disconnectReasonString);
// }
}
}
} finally {
if (dtook) {
dmtx.give();
}
// mtx.give();
}
// request larger mtu(请求更大的MTU)
if (Platform.isAndroid && isConnected && mtu != null) {
await requestMtu(mtu);
}
}
连接的流程图
Dart
开始连接
↓
检查参数(mtu 和 autoConnect 不能同时用)
↓
获取互斥锁(防止和 disconnect 冲突)
↓
[autoConnect = true] → 加入自动重连名单
↓
构建请求参数
↓
开始监听连接状态(避免错过响应)
↓
调用原生代码连接
↓
释放互斥锁(允许 disconnect)
↓
┌─────────────────────────────────┐
│ autoConnect = false? │
└─────────────────────────────────┘
↓ 是 ↓ 否
等待连接结果 立即返回
检查超时 (不等结果)
超时则自动断开
↓
连接成功
↓
[Android + mtu不为空] → 请求更大 MTU
↓
结束
断联方法
Dart
/// Cancels connection to the Bluetooth Device
/// - [queue] If true, this disconnect request will be executed after all other operations complete.
/// If false, this disconnect request will be executed right now, i.e. skipping to the front
/// of the fbp operation queue, which is useful to cancel an in-progress connection attempt.
Future<void> disconnect({int timeout = 35, bool queue = true}) async {
// Only allow a single disconnect operation at a time
//互斥锁控制并发-确保同时只有一个disconnect操作执行
_Mutex dtx = _MutexFactory.getMutexForKey("disconnect");
await dtx.take();
// Only allow a single ble operation to be underway at a time?
// queue = false,立即执行,跳过队列
_Mutex mtx = _MutexFactory.getMutexForKey("global");
if (queue) { //等待所有其他蓝牙操作完成后再执行断开
await mtx.take();
}
try {
// remove from auto connect list if there - 取消自动重连
FlutterBluePlus._autoConnect.remove(remoteId);
//监听断开状态变化
var responseStream = FlutterBluePlus._methodStream.stream
.where((m) => m.method == "OnConnectionStateChanged")
.map((m) => m.arguments)
.map((args) => BmConnectionStateResponse.fromMap(args))
.where((p) => p.remoteId == remoteId)
.where((p) => p.connectionState == BmConnectionStateEnum.disconnected);
// Start listening now, before invokeMethod, to ensure we don't miss the response
//提前订阅-在调用断开方法前就开始监听,确保不会错过断开事件
Future<BmConnectionStateResponse> futureState = responseStream.first;
// invoke - 执行断开操作-调用原生平台的断开方法
bool changed = await FlutterBluePlus._invokeMethod('disconnect', remoteId.str);
// only wait for disconnection if weren't already disconnected
if (changed) { //等待断开完成
await futureState.fbpEnsureAdapterIsOn("disconnect").fbpTimeout(timeout, "disconnect");
}
} finally {
dtx.give();
if (queue) {
mtx.give();
}
}
}
使用场景
Dart
// 正常断开,排队等待其他操作完成
await device.disconnect();
// 紧急取消正在进行的连接尝试(不排队)
await device.disconnect(queue: false, timeout: 10);
// 自定义超时时间
await device.disconnect(timeout: 20);
发现服务
Dart
/// Discover services, characteristics, and descriptors of the remote device
/// - [subscribeToServicesChanged] Android Only: If true, after discovering services we will subscribe
/// to the Services Changed Characteristic (0x2A05) used for the `device.onServicesReset` stream.
/// Note: this behavior happens automatically on iOS and cannot be disabled
Future<List<BluetoothService>> discoverServices({
bool subscribeToServicesChanged = true, //Android专用,订阅服务变化通知
int timeout = 15 //超时时间
}) async {
// check connected - 连接状态检查
if (isDisconnected) { //断开则抛出异常
throw FlutterBluePlusException(
ErrorPlatform.fbp, "discoverServices", FbpErrorCode.deviceIsDisconnected.index, "device is not connected");
}
// Only allow a single ble operation to be underway at a time
//全局互斥锁 - 确保同一时间只有一个蓝牙操作执行
_Mutex mtx = _MutexFactory.getMutexForKey("global");
await mtx.take();
List<BluetoothService> result = [];
try {
//创造响应监听流
var responseStream = FlutterBluePlus._methodStream.stream
.where((m) => m.method == "OnDiscoveredServices") //匹配方法名
.map((m) => m.arguments) //提取参数
.map((args) => BmDiscoverServicesResult.fromMap(args)) //反序列化
.where((p) => p.remoteId == remoteId); //过滤当前设备
// Start listening now, before invokeMethod, to ensure we don't miss the response
//提前订阅通知
Future<BmDiscoverServicesResult> futureResponse = responseStream.first;
// invoke - 触发原生发现服务 - 调用原生平台监听,避免事件丢失
await FlutterBluePlus._invokeMethod('discoverServices', remoteId.str);
// wait for response - 等待响应并处理
BmDiscoverServicesResult response = await futureResponse
.fbpEnsureAdapterIsOn("discoverServices") //确保蓝牙开启
.fbpEnsureDeviceIsConnected(this, "discoverServices") //确保设备连接
.fbpTimeout(timeout, "discoverServices"); //超时控制
// failed?
if (!response.success) {// 失败则抛出异常
throw FlutterBluePlusException(_nativeError, "discoverServices", response.errorCode, response.errorString);
}
//成功则将protobuf格式转换为Dart对象
result = response.services.map((p) => BluetoothService.fromProto(p)).toList();
} finally {
mtx.give();
}
// in order to match iOS behavior on all platforms,
// we always listen to the Services Changed characteristic if it exists.
if (subscribeToServicesChanged) { //服务变化
if (Platform.isIOS == false && Platform.isMacOS == false) { //筛选非IOS设备
BluetoothCharacteristic? c = _servicesChangedCharacteristic; //服务特征
//存在,支持通知,还没订阅
if (c != null && (c.properties.notify || c.properties.indicate) && c.isNotifying == false) {
await c.setNotifyValue(true); //订阅通知
}
}
}
return result;
}
获取rssi
Dart
/// Read the RSSI of connected remote device
//RSSI值(负整数),值的范围通常 -100 dBm 到 0 dBm,值越大越好
Future<int> readRssi({int timeout = 15}) async {
// check connected
if (isDisconnected) { //连接状态检查
throw FlutterBluePlusException(
ErrorPlatform.fbp, "readRssi", FbpErrorCode.deviceIsDisconnected.index, "device is not connected");
}
// Only allow a single ble operation to be underway at a time
//全局互斥锁,确保同一时间只有一个蓝牙操作
_Mutex mtx = _MutexFactory.getMutexForKey("global");
await mtx.take();
int rssi = 0;
try {
//创建响应监听流
var responseStream = FlutterBluePlus._methodStream.stream
.where((m) => m.method == "OnReadRssi") //匹配RSSI读取完成事件
.map((m) => m.arguments) //提取参数
.map((args) => BmReadRssiResult.fromMap(args)) //反序列化
.where((p) => (p.remoteId == remoteId)); /只关心当前设备
// Start listening now, before invokeMethod, to ensure we don't miss the response
Future<BmReadRssiResult> futureResponse = responseStream.first;//提前订阅
// invoke - 调用原生平台的API读取RSSI
await FlutterBluePlus._invokeMethod('readRssi', remoteId.str);
// wait for response - 等待响应并处理
BmReadRssiResult response = await futureResponse
.fbpEnsureAdapterIsOn("readRssi") //确保蓝牙设配器开启
.fbpEnsureDeviceIsConnected(this, "readRssi") //确保设备仍连接
.fbpTimeout(timeout, "readRssi"); //超时保护
// failed?
if (!response.success) { //原生操作失败则抛出异常
throw FlutterBluePlusException(_nativeError, "readRssi", response.errorCode, response.errorString);
}
rssi = response.rssi; //成功则提取RSSI值
} finally {
mtx.give(); //释放互斥锁
}
return rssi;
}
请求改变MTU大小
Dart
/// Request to change MTU (Android Only) - 请求改变MTU
/// - returns new MTU - 返回新的MTU
/// - [predelay] adds delay to avoid race conditions on some devices. see comments below.
//参数:期望的MTU值,延迟事件,超时时间
Future<int> requestMtu(int desiredMtu, {double predelay = 0.35, int timeout = 15}) async {
// check android - 过滤非安卓设备
if (Platform.isAndroid == false) {
throw FlutterBluePlusException(ErrorPlatform.fbp, "requestMtu", FbpErrorCode.androidOnly.index, "android-only");
}
// check connected - 检查连接状态
if (isDisconnected) {
throw FlutterBluePlusException(
ErrorPlatform.fbp, "requestMtu", FbpErrorCode.deviceIsDisconnected.index, "device is not connected");
}
// Only allow a single ble operation to be underway at a time
//全局互斥锁
_Mutex mtx = _MutexFactory.getMutexForKey("global");
await mtx.take();
// predelay - 预延迟机制
if (predelay > 0) {
// hack: By adding delay before we call `requestMtu`, we can avoid
// a race condition that can cause `discoverServices` to timeout or fail.
//
// Note: This hack is only needed for devices that automatically send an
// MTU update right after connection. If your device does not do that,
// you can set this delay to zero. Other people may need to increase it!
//
// The race condition goes like this:
// 1. you call `requestMtu` right after connection
// 2. some devices automatically send a new MTU right after connection, without being asked
// 3. your call to `requestMtu` confuses the results from step 1 and step 2, and returns to early
// 4. the user then calls `discoverServices`, thinking that `requestMtu` has finished
// 5. in reality, `requestMtu` is still happening, and the call to `discoverServices` will fail/timeout
//
// Adding delay before we call `requestMtu` helps ensure
// that the automatic mtu update has already happened.
await Future.delayed(Duration(milliseconds: (predelay * 1000).toInt()));
}
var mtu = 0;
try {
var request = BmMtuChangeRequest(
remoteId: remoteId,
mtu: desiredMtu,
);
var responseStream = FlutterBluePlus._methodStream.stream
.where((m) => m.method == "OnMtuChanged")
.map((m) => m.arguments)
.map((args) => BmMtuChangedResponse.fromMap(args))
.where((p) => p.remoteId == remoteId)
.map((p) => p.mtu);
// Start listening now, before invokeMethod, to ensure we don't miss the response
Future<int> futureResponse = responseStream.first;
// invoke
await FlutterBluePlus._invokeMethod('requestMtu', request.toMap());
// wait for response
mtu = await futureResponse
.fbpEnsureAdapterIsOn("requestMtu")
.fbpEnsureDeviceIsConnected(this, "requestMtu")
.fbpTimeout(timeout, "requestMtu");
} finally {
mtx.give();
}
return mtu;
}