BluetoothDevice

源码解析

构造函数
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;
  }
相关推荐
一块小土坷垃3 小时前
BuhoCleaner 1.15.6 深度体验:macOS 系统清理与卸载优化工具评测
macos·开源软件
Youyzq4 小时前
安装龙虾流程mac
macos·openclaw
ACGkaka_6 小时前
JDK 版本管理工具介绍:jenv与sdkman(Mac端)
java·macos·sdkman
库奇噜啦呼6 小时前
【iOS】 Blocks
macos·ios·cocoa
一块小土坷垃7 小时前
Pearcleaner:一款功能强大的免费开源 macOS 应用清理工具
macos·开源软件
承渊政道7 小时前
【递归、搜索与回溯算法】(递归问题拆解与经典模型实战大秘笈)
数据结构·c++·学习·算法·macos·dfs·bfs
中国胖子风清扬7 小时前
基于GPUI框架构建现代化待办事项应用:从架构设计到业务落地
java·spring boot·macos·小程序·rust·uni-app·web app
yuanzhengme1 天前
AI【应用 04】FunASR离线文件转写服务开发指南(实践篇)
人工智能·macos·xcode
x-cmd1 天前
[260412] x-cmd v0.8.13:x free 新增进程内存显示,feishu、telegram REPL 机器人齐上线!
linux·macos·机器人·内存·x-cmd·telegram·feishu