变量static bool _initialized false; //初始化标志 /// native platform channel - 与原生平台通信的通道 static final MethodChannel _methodChannel const MethodChannel(flutter_blue_plus/methods); /// a broadcast stream version of the MethodChannel // ignore: close_sinks //接收原平台主动发来的事件 static final StreamControllerMethodCall _methodStream StreamController.broadcast(); // always keep track of these device variables //设备状态缓存 static final MapDeviceIdentifier, BmConnectionStateResponse _connectionStates {}; //服务列表 static final MapDeviceIdentifier, BmDiscoverServicesResult _knownServices {}; //配对状态 static final MapDeviceIdentifier, BmBondStateResponse _bondStates {}; //保存MTU的值 static final MapDeviceIdentifier, BmMtuChangedResponse _mtuValues {}; //保存设备名称 static final MapDeviceIdentifier, String _platformNames {}; //系统名称 static final MapDeviceIdentifier, String _advNames {}; //广播名称 //最后的读写特征 static final MapDeviceIdentifier, MapString, Listint _lastChrs {}; static final MapDeviceIdentifier, MapString, Listint _lastDescs {}; //订阅管理 static final MapDeviceIdentifier, ListStreamSubscription _deviceSubscriptions {}; static final MapDeviceIdentifier, ListStreamSubscription _delayedSubscriptions {}; static final ListStreamSubscription _scanSubscriptions []; //自动连接集合 static final SetDeviceIdentifier _autoConnect {}; //扫描相关 /// stream used for the isScanning public api static final _isScanning _StreamControllerReEmitbool(initialValue: false); /// stream used for the scanResults public api static final _scanResults _StreamControllerReEmitListScanResult(initialValue: []); /// buffers the scan results static _BufferStreamBmScanResponse? _scanBuffer; /// the subscription to the merged scan results stream static StreamSubscriptionBmScanResponse?? _scanSubscription; /// timeout for scanning that can be cancelled by stopScan static Timer? _scanTimeout; /// the last known adapter state - 适配器状态 static BmAdapterStateEnum? _adapterStateNow; /// FlutterBluePlus log level - 日志配置 static LogLevel _logLevel LogLevel.debug; static bool _logColor true;get方法//获取当前日志级别 static LogLevel get logLevel _logLevel; /// Checks whether the hardware supports Bluetooth - 硬件是否支持蓝牙 static Futurebool get isSupported async await _invokeMethod(isSupported); /// The current adapter state - 当前蓝牙适配器状态 static BluetoothAdapterState get adapterStateNow _adapterStateNow ! null ? _bmToAdapterState(_adapterStateNow!) : BluetoothAdapterState.unknown; /// Return the friendly Bluetooth name of the local Bluetooth adapter - 适配器名称 static FutureString get adapterName async await _invokeMethod(getAdapterName); /// returns whether we are scanning as a stream - 扫描状态 static Streambool get isScanning _isScanning.stream; /// are we scanning right now? - 当前时刻是否正在扫描 static bool get isScanningNow _isScanning.latestValue; /// the most recent scan results - 最近一次扫描的结果列表 static ListScanResult get lastScanResults _scanResults.latestValue; /// a stream of scan results - 作为流返回扫描结果 //重新监听时会重新发送之前的结果 /// - if you re-listen to the stream it re-emits the previous results //包含从扫描开始以来的所有结果 /// - the list contains all the results since the scan started /// - the returned stream is never closed.返回的流永远不会关闭 static StreamListScanResult get scanResults _scanResults.stream; /// This is the same as scanResults, except: /// - it *does not* re-emit previous results after scanning stops. static StreamListScanResult get onScanResults { if (isScanningNow) { return _scanResults.stream;//正常返回扫描结果流 } else { // skip previous results push empty list //跳过之前的缓存结果先发送空列表[],然后才发送新结果 return _scanResults.stream.skip(1).newStreamWithInitialValue([]); } }疑问点1.蓝牙适配器是什么作用是什么 答蓝牙适配器是指设备上负责管理蓝牙功能的硬件模块及其对应的软件接口。 核心作用管理蓝牙开关状态设备识别与命名扫描周围设备管理连接。访问蓝牙设备事件流/// Get access to all device event streams //蓝牙设备事件流的统一访问入口 static final BluetoothEvents events BluetoothEvents();开启蓝牙的警告框//当蓝牙不可用时系统是否自动弹出体系用户开启蓝牙的警告框 /// Set configurable options - 只能在IOS和MacOS系统上使用 /// - [showPowerAlert] Whether to show the power alert (iOS MacOS only). i.e. CBCentralManagerOptionShowPowerAlertKey /// To set this option you must call this method before any other method in this package. /// See: https://developer.apple.com/documentation/corebluetooth/cbcentralmanageroptionshowpoweralertkey /// This option has no effect on Android. static Futurevoid setOptions({ bool showPowerAlert true, }) async { await _invokeMethod(setOptions, {show_power_alert: showPowerAlert}); }打开蓝牙/// Turn on Bluetooth (Android only),只安卓设备有效 static Futurevoid turnOn({int timeout 60}) async { //设置响应监听 var responseStream FlutterBluePlus._methodStream.stream .where((m) m.method OnTurnOnResponse) .map((m) m.arguments) .map((args) BmTurnOnResponse.fromMap(args)); // Start listening now, before invokeMethod, to ensure we dont miss the response //提前开始监听 FutureBmTurnOnResponse futureResponse responseStream.first; // invoke - 调用原生方法 bool changed await _invokeMethod(turnOn); // only wait if bluetooth was off 只在蓝牙原本关闭时执行 if (changed) { // wait for response - 等待用户选择 BmTurnOnResponse response await futureResponse.fbpTimeout(timeout, turnOn); // check response 检查授权结果 if (response.userAccepted false) { throw FlutterBluePlusException(ErrorPlatform.fbp, turnOn, FbpErrorCode.userRejected.index, user rejected); } // wait for adapter to turn on - 等待蓝牙完全开启 await adapterState.where((s) s BluetoothAdapterState.on).first.fbpTimeout(timeout, turnOn); } }交互时序图用户代码 Flutter层 原生层(Android) 系统对话框 | | | | |---turnOn()----------| | | | |---监听响应(提前)--------| | | | | | | |---_invokeMethod--------| | | | (turnOn) | | | | |---显示对话框----------| | | | | | | |--用户点击允许-------| | |--OnTurnOnResponse------| | | | (userAcceptedtrue) | | | | | | | |--adapterStateon-------| | |--(完成)-------------| | |异步生成器/// Gets the current state of the Bluetooth module //提供蓝牙适配器状态的持续监控流 static StreamBluetoothAdapterState get adapterState async* { // get current state if needed 获取初始状态 if (_adapterStateNow null) { var result await _invokeMethod(getAdapterState); var value BmBluetoothAdapterState.fromMap(result).adapterState; // update _adapterStateNow if it is still null after the await //双重检查防止并发问题 if (_adapterStateNow null) { _adapterStateNow value; } } //创建状态变化监听流 yield* FlutterBluePlus._methodStream.stream .where((m) m.method OnAdapterStateChanged) .map((m) m.arguments) .map((args) BmBluetoothAdapterState.fromMap(args)) .map((s) _bmToAdapterState(s.adapterState)) .newStreamWithInitialValue(_bmToAdapterState(_adapterStateNow!)); }连接的蓝牙设备列表/// Retrieve a list of devices currently connected to your app //获取当前应用已经连接的蓝牙设备列表当前软件连接的BLE设备 static ListBluetoothDevice get connectedDevices { var copy Map.from(_connectionStates); //复制连接状态映射 //删除未连接的设备 copy.removeWhere((key, value) value.connectionState BmConnectionStateEnum.disconnected); //转换未设备对象列表 return copy.values.map((v) BluetoothDevice(remoteId: v.remoteId)).toList(); }系统中所有已经连接的蓝牙设备/// Retrieve a list of devices currently connected to the system /// - The list includes devices connected to by *any* app /// - You must still call device.connect() to connect them to *your app* //返回系统中所有已连接的蓝牙设备列表不仅限于当前应用连接的设备 //整个手机连接的BLE设备 static FutureListBluetoothDevice get systemDevices async { var result await _invokeMethod(getSystemDevices);//调用原生方法 var r BmDevicesList.fromMap(result); //解析结果 for (BmBluetoothDevice device in r.devices) { //缓存设备名称 if (device.platformName ! null) { _platformNames[device.remoteId] device.platformName!; } } return r.devices.map((d) BluetoothDevice.fromProto(d)).toList(); //转换为设备对象 }获取已配对的蓝牙设备列表/// Retrieve a list of bonded devices (Android only) //获取已配对的蓝牙设备列表连接BLE的设备连接经典蓝牙的设备 static FutureListBluetoothDevice get bondedDevices async { var result await _invokeMethod(getBondedDevices);//调用原生方法 var r BmDevicesList.fromMap(result); //解析结果 for (BmBluetoothDevice device in r.devices) { //缓存设备名称 if (device.platformName ! null) { _platformNames[device.remoteId] device.platformName!; } } return r.devices.map((d) BluetoothDevice.fromProto(d)).toList(); //返回设备列表 }扫描方法/// Start a scan, and return a stream of results 开始扫描并返回结果流 /// Note: scan filters use an or behavior. i.e. if you set withServices withNames we //扫描过滤器使用‘或’行为 /// return all the advertisments that match any of the specified services *or* any of the specified names. /// - [withServices] filter by advertised services /// - [withRemoteIds] filter for known remoteIds (iOS: 128-bit guid, android: 48-bit mac address) /// - [withNames] filter by advertised names (exact match) /// - [withKeywords] filter by advertised names (matches any substring) /// - [withMsd] filter by manfacture specific data /// - [withServiceData] filter by service data /// - [timeout] calls stopScan after a specified duration /// - [removeIfGone] if true, remove devices after theyve stopped advertising for X duration /// - [continuousUpdates] If true, we continually update lastSeen rssi by processing //如果为 true我们通过处理重复的广播数据来持续更新 lastSeen 和 rssi。这会消耗更多电量。通常不建议使用此选项。 /// duplicate advertisements. This takes more power. You typically should not use this option. /// - [continuousDivisor] Useful to help performance. If divisor is 3, then two-thirds of advertisements are /// ignored, and one-third are processed. This reduces main-thread usage caused by the platform channel. /// The scan counting is per-device so you always get the 1st advertisement from each device. /// If divisor is 1, all advertisements are returned. This argument only matters for continuousUpdates mode. /// - [oneByOne] if true, we will stream every advertistment one by one, possibly including duplicates. /// If false, we deduplicate the advertisements, and return a list of devices. /// - [androidScanMode] choose the android scan mode to use when scanning /// - [androidUsesFineLocation] request ACCESS_FINE_LOCATION permission at runtime static Futurevoid startScan({ //过滤参数 ListGuid withServices const [], //按服务UUID过滤 ListString withRemoteIds const [], //按设备ID过滤IOSGUIDAndroid:MAC地址 ListString withNames const [], //按设备名称过滤精准匹配 ListString withKeywords const [],//按关键词过滤子串匹配 ListMsdFilter withMsd const [], //按服务商特定数据过滤 ListServiceDataFilter withServiceData const [], //按服务数据过滤 //行为参数 Duration? timeout, //自动停止扫描的时长 Duration? removeIfGone,//设备停止广播X时长后移除 bool continuousUpdates false,//是否持续更新重复广告 int continuousDivisor 1,//性能优化每N条广告处理1条 bool oneByOne false,//true逐个返回设备false:批量返回列表 //Android专用参数 AndroidScanMode androidScanMode AndroidScanMode.lowLatency,//扫描模式 bool androidUsesFineLocation false, //是否需要精确定位权限 }) async { // check args - 参数合法性检查 //如果设置了 removeIfGone不为 null则必须同时设置 continuousUpdates true assert(removeIfGone null || continuousUpdates, removeIfGone requires continuousUpdates); //不能同时使用 removeIfGone 和 oneByOne true assert(removeIfGone null || !oneByOne, removeIfGone is not compatible with oneByOne); //continuousDivisor 必须是大于等于 1 的整数 assert(continuousDivisor 1, divisor must be 1); // check filters - 检查是否设置了除withKeywords以外的其他过滤器 bool hasOtherFilter withServices.isNotEmpty || withRemoteIds.isNotEmpty || withNames.isNotEmpty || withMsd.isNotEmpty || withServiceData.isNotEmpty; // Note: withKeywords is not compatible with other filters on android //注意withKeywords 在 Android 上与其他过滤器不兼容 // because it is implemented in custom fbp code, not android code // 因为它是在自定义的 fbp 代码中实现的而不是在 Android 原生代码中 assert(!(Platform.isAndroid withKeywords.isNotEmpty hasOtherFilter), withKeywords is not compatible with other filters on Android); // only allow a single task to call // startScan or stopScan at a time _Mutex mtx _MutexFactory.getMutexForKey(scan); //互斥锁控制 await mtx.take(); try { // already scanning? if (_isScanning.latestValue true) { // stop existing scan await _stopScan(); } else { // push to stream _isScanning.add(true); } //创造扫描配置对象 var settings BmScanSettings( withServices: withServices, withRemoteIds: withRemoteIds, withNames: withNames, withKeywords: withKeywords, withMsd: withMsd.map((d) d._bm).toList(), withServiceData: withServiceData.map((d) d._bm).toList(), continuousUpdates: continuousUpdates, continuousDivisor: continuousDivisor, androidScanMode: androidScanMode.value, androidUsesFineLocation: androidUsesFineLocation); //创建响应流监听 StreamBmScanResponse responseStream FlutterBluePlus._methodStream.stream .where((m) m.method OnScanResponse) //只接收扫描响应 .map((m) m.arguments) .map((args) BmScanResponse.fromMap(args)); // Start listening now, before invokeMethod, so we do not miss any results _scanBuffer _BufferStream.listen(responseStream);//开始监听 // invoke platform method - 调用原生扫描 await _invokeMethod(startScan, settings.toMap()).onError((e, s) _stopScan(invokePlatform: false)); // check every 250ms for gone devices? - 处理设备过期 late StreamBmScanResponse? outputStream removeIfGone ! null ? _mergeStreams([_scanBuffer!.stream, Stream.periodic(Duration(milliseconds: 250))]) : _scanBuffer!.stream; // start by pushing an empty array _scanResults.add([]); ListScanResult output []; // listen push to scanResults stream - 处理扫描结果 _scanSubscription outputStream.listen((BmScanResponse? response) { if (response null) { // if null, this is just a periodic update to remove old results //定期检查移除过期设备 if (output._removeWhere((elm) DateTime.now().difference(elm.timeStamp) removeIfGone!)) { _scanResults.add(List.from(output)); // push to stream } } else { // failure? 错误处理 if (response.success false) { var e FlutterBluePlusException(_nativeError, scan, response.errorCode, response.errorString); _scanResults.addError(e);//向流中添加错误 _stopScan(invokePlatform: false);//停止扫描不调用原生层 } // iterate through advertisements for (BmScanAdvertisement bm in response.advertisements) {//遍历广播数据 // cache platform name - 缓存设备名称 if (bm.platformName ! null) { _platformNames[bm.remoteId] bm.platformName!; } // cache advertised name - 缓存广播名称 if (bm.advName ! null) { _advNames[bm.remoteId] bm.advName!; } // convert - 转换为ScanResult的对象 ScanResult sr ScanResult.fromProto(bm); if (oneByOne) { // push single item _scanResults.add([sr]); //每次只推送一个设备的列表 } else { // add result to output output.addOrUpdate(sr);//更新设备列表 } } // push entire list 批量推送 if (!oneByOne) { _scanResults.add(List.from(output)); } } }); // Start timer *after* stream is being listened to, to make sure the // timeout does not fire before _scanSubscription is set if (timeout ! null) { _scanTimeout Timer(timeout, stopScan); //设置扫描超时定时器 } } finally { mtx.give(); //解锁 } }停止扫描/// Stops a scan for Bluetooth Low Energy devices //给外部调用的API负责安全的停止扫描 static Futurevoid stopScan() async { _Mutex mtx _MutexFactory.getMutexForKey(scan); //互斥锁 await mtx.take(); try { if(isScanningNow) { //只有在扫描中才停止 await _stopScan(); } else if (_logLevel.index LogLevel.info.index) { print([FBP] stopScan: already stopped); } } finally { mtx.give(); //释放锁 } } /// for internal use //内部实现的停止扫描方法 static Futurevoid _stopScan({bool invokePlatform true}) async { _scanBuffer?.close(); //关闭缓冲区 _scanSubscription?.cancel();//取消扫描订阅 _scanTimeout?.cancel(); //取消超时定时器 _isScanning.add(false); //更新状态为未扫描 for (var subscription in _scanSubscriptions) { subscription.cancel();//取消所有其他订阅 } if (invokePlatform) { await _invokeMethod(stopScan);//通知原生层停止 } }清理步骤┌─────────────────────────────────────────────────────────┐ │ _stopScan() 清理流程 │ ├─────────────────────────────────────────────────────────┤ │ │ │ 1. _scanBuffer?.close() │ │ └─ 关闭结果缓冲区停止接收新数据 │ │ │ │ 2. _scanSubscription?.cancel() │ │ └─ 取消扫描结果的订阅停止处理 │ │ │ │ 3. _scanTimeout?.cancel() │ │ └─ 取消超时定时器防止超时后重复停止 │ │ │ │ 4. _isScanning.add(false) │ │ └─ 更新状态通知所有监听者扫描已停止 │ │ │ │ 5. 遍历取消 _scanSubscriptions │ │ └─ 清理所有其他相关订阅如各设备的连接状态 │ │ │ │ 6. _invokeMethod(stopScan) │ │ └─ 通知原生层停止扫描可选 │ │ │ └─────────────────────────────────────────────────────────┘管理流订阅的生命周期/// Register a subscription to be canceled when scanning is complete. /// This function simplifies cleanup, to prevent creating duplicate stream subscriptions. /// - this is an optional convenience function /// - prevents accidentally creating duplicate subscriptions before each scan //注册一个订阅在扫描完成时自动取消这个函数简化了清理工作防止创建重复的流订阅 //这是一个可选的便捷函数防止在每次扫描前意外创建重复订阅 static void cancelWhenScanComplete(StreamSubscription subscription) { FlutterBluePlus._scanSubscriptions.add(subscription); }设置FlutterBluePlus插件的日志级别/// Sets the internal FlutterBlue log level //设置FlutterBluePlus内部的日志级别 static Futurevoid setLogLevel(LogLevel level, {color true}) async { _logLevel level; //保存日志级别到内存 _logColor color; //保存是否使用彩色日志 await _invokeMethod(setLogLevel, level.index);//通知原生层 }获取蓝牙物理层PHY支持信息/// Request Bluetooth PHY support 请求蓝牙PHY支持信息 static FuturePhySupport getPhySupport() async { // check android 过滤出安卓设备 if (Platform.isAndroid false) { throw FlutterBluePlusException( ErrorPlatform.fbp, getPhySupport, FbpErrorCode.androidOnly.index, android-only); } //调用原生方法并转换结果 return await _invokeMethod(getPhySupport).then((args) PhySupport.fromMap(args));初始化方法static Futuredynamic _initFlutterBluePlus() async { if (_initialized) { return; } _initialized true; // set platform method handler 设置平台方法调用处理器 _methodChannel.setMethodCallHandler(_methodCallHandler); // hot restart 处理热重启情况 if ((await _methodChannel.invokeMethod(flutterHotRestart)) ! 0) { await Future.delayed(Duration(milliseconds: 50)); while ((await _methodChannel.invokeMethod(connectedCount)) ! 0) { await Future.delayed(Duration(milliseconds: 50)); } } }方法调用处理器static Futuredynamic _methodCallHandler(MethodCall call) async { // log result if (logLevel LogLevel.verbose) { //日志级别设置为详细模式 String func [[ ${call.method} ]]; String result call.arguments.toString(); func _logColor ? _black(func) : func; result _logColor ? _brown(result) : result; print([FBP] $func result: $result); } // android only if (call.method OnDetachedFromEngine) { _stopScan(invokePlatform: false); } // keep track of adapter states if (call.method OnAdapterStateChanged) { BmBluetoothAdapterState r BmBluetoothAdapterState.fromMap(call.arguments); _adapterStateNow r.adapterState; if (isScanningNow r.adapterState ! BmAdapterStateEnum.on) { _stopScan(invokePlatform: false); } if (r.adapterState BmAdapterStateEnum.on) { for (DeviceIdentifier d in _autoConnect) { BluetoothDevice(remoteId: d).connect(autoConnect: true, mtu: null).onError((e, s) { if (logLevel ! LogLevel.none) { print([FBP] [AutoConnect] connection failed: $e); } }); } } } // keep track of connection states if (call.method OnConnectionStateChanged) { var r BmConnectionStateResponse.fromMap(call.arguments); _connectionStates[r.remoteId] r; if (r.connectionState BmConnectionStateEnum.disconnected) { // push to mtu stream, if needed if (_mtuValues.containsKey(r.remoteId)) { var resp BmMtuChangedResponse(remoteId: r.remoteId, mtu: 23); _methodStream.add(MethodCall(OnMtuChanged, resp.toMap())); } // clear mtu _mtuValues.remove(r.remoteId); // clear lastDescs (resets isNotifying) _lastDescs.remove(r.remoteId); // clear lastChrs (api consistency) _lastChrs.remove(r.remoteId); // cancel delete subscriptions _deviceSubscriptions[r.remoteId]?.forEach((s) s.cancel()); _deviceSubscriptions.remove(r.remoteId); // Note: to make FBP easier to use, we do not clear knownServices, // otherwise servicesList would be more annoying to use. We also // do not clear bondState, for faster performance. // autoconnect if (Platform.isAndroid false) { if (_autoConnect.contains(r.remoteId)) { if (_adapterStateNow BmAdapterStateEnum.on) { var d BluetoothDevice(remoteId: r.remoteId); d.connect(autoConnect: true, mtu: null).onError((e, s) { if (logLevel ! LogLevel.none) { print([FBP] [AutoConnect] connection failed: $e); } }); } } } } } // keep track of device name if (call.method OnNameChanged) { var device BmNameChanged.fromMap(call.arguments); if (Platform.isMacOS || Platform.isIOS) { // iOS macOS internally use the name changed callback for the platform name _platformNames[device.remoteId] device.name; } } // keep track of services resets if (call.method OnServicesReset) { var r BmBluetoothDevice.fromMap(call.arguments); _knownServices.remove(r.remoteId); } // keep track of bond state if (call.method OnBondStateChanged) { var r BmBondStateResponse.fromMap(call.arguments); _bondStates[r.remoteId] r; } // keep track of services if (call.method OnDiscoveredServices) { var r BmDiscoverServicesResult.fromMap(call.arguments); if (r.success true) { _knownServices[r.remoteId] r; } } // keep track of mtu values if (call.method OnMtuChanged) { var r BmMtuChangedResponse.fromMap(call.arguments); if (r.success true) { _mtuValues[r.remoteId] r; } } // keep track of characteristic values if (call.method OnCharacteristicReceived || call.method OnCharacteristicWritten) { var r BmCharacteristicData.fromMap(call.arguments); if (r.success true) { _lastChrs[r.remoteId] ?? {}; _lastChrs[r.remoteId]![${r.serviceUuid}:${r.characteristicUuid}] r.value; } } // keep track of descriptor values if (call.method OnDescriptorRead || call.method OnDescriptorWritten) { var r BmDescriptorData.fromMap(call.arguments); if (r.success true) { _lastDescs[r.remoteId] ?? {}; _lastDescs[r.remoteId]![${r.serviceUuid}:${r.characteristicUuid}:${r.descriptorUuid}] r.value; } } _methodStream.add(call); // cancel delayed subscriptions if (call.method OnConnectionStateChanged) { if (_delayedSubscriptions.isNotEmpty) { var r BmConnectionStateResponse.fromMap(call.arguments); if (r.connectionState BmConnectionStateEnum.disconnected) { var remoteId r.remoteId; // use delayed to update the stream before we cancel it Future.delayed(Duration.zero).then((_) { _delayedSubscriptions[remoteId]?.forEach((s) s.cancel()); // cancel _delayedSubscriptions.remove(remoteId); // delete }); } } } }