蓝牙连接与状态管理
本页介绍 Flutter-JL_Home 中蓝牙设备连接与状态管理的完整链路:Dart 侧的连接指令入口 BleConnectionManager、连接状态模型 DeviceConnection、事件流处理器 BleDeviceConnectionProcessor,以及应用层全局状态管理 ConnectionStateManager,涵盖从扫描、连接、断开到状态回调的端到端机制。
Purpose and Scope
本页覆盖「蓝牙连接与状态管理」这一能力边界内的全部实现:
- 连接指令:扫描启动/停止、按索引连接/断开设备的指令下发(
BleConnectionManager)。 - 连接状态模型:原生侧回传的连接状态数据的 Dart 表示(
DeviceConnection)。 - 事件流处理:将原生事件转换为 Dart
Stream的扫描状态、设备列表、连接状态流(BleDeviceConnectionProcessor)。 - 应用层状态:example 工程中基于
ChangeNotifier的全局连接状态单例(ConnectionStateManager)及其配套的全局监听工具(global_connection_listener.dart)。
以下主题属于兄弟页面,不在本页展开:音频播放与解码、文件管理、OTA 升级、扫描发现之外的设备服务(如通话、音乐控制)等。若相关目录页存在,请参见对应页面。
概述
Flutter-JL_Home 采用「Dart 插件层 + Android 原生 SDK」的双端架构。蓝牙连接的典型旅程是:用户在 UI 中触发扫描 → 原生 BLE 协议栈回报扫描结果 → 用户选择设备并下发连接指令 → 原生层建立 GATT 连接 → 连接状态以事件形式回传 Dart 层 → 应用刷新全局状态并驱动界面。
这套能力被刻意设计为无状态命令式入口 + 事件流式回调的组合:
- 指令方向(Dart → Native):所有动作都通过
BleBaseManager.invokeMethod()走 Flutter MethodChannel,方法名集中在BleMethodConstants。 - 事件方向(Native → Dart):原生层把扫描结果、连接状态等通过事件通道推回,
BleBaseEventProcessor负责按事件类型过滤,BleDeviceConnectionProcessor再将其映射为类型安全的Stream。
应用层拿到这些 Stream 后,既可以直接订阅驱动 UI,也可以通过 ConnectionStateManager 这类单例把「最新状态」集中缓存,避免多个页面各自维护状态副本。这种「命令 + 事件流」的分工,使得连接逻辑与 UI 解耦,且天然支持多页面共享同一连接会话。
架构
flowchart TD
subgraph sg_UI["Flutter 应用层 (example)"]
UI["UI Widgets"]
CSM["ConnectionStateManager<br/>(ChangeNotifier 单例)"]
GCL["GlobalConnectionListener"]
end
subgraph sg_Dart["Dart 插件层 (lib)"]
BCM["BleConnectionManager<br/>(静态指令)"]
BCP["BleDeviceConnectionProcessor<br/>(静态事件流)"]
BBM["BleBaseManager<br/>(MethodChannel invokeMethod)"]
BEP["BleBaseEventProcessor<br/>(事件过滤/解析)"]
end
subgraph sg_Native["Android 原生层 (Kotlin/Java)"]
DCM["DeviceConnectionManager.kt"]
DC["DeviceConnection.java"]
DCS["DoubleConnectionSp.java<br/>(双连接配置持久化)"]
BLE["Android BLE 协议栈"]
end
UI -->|"订阅/刷新"| CSM
CSM -->|"updateConnectState"| GCL
GCL -->|"订阅事件流"| BCP
UI -->|"startScan/connectDevice"| BCM
BCM -->|"invokeMethod"| BBM
BBM -->|"MethodChannel"| DCM
DCM --> BLE
DCM --> DC
DCM --> DCS
BLE -->|"原生事件回调"| BEP
BEP -->|"filterByType + 解析"| BCP
BCP -->|"类型安全 Stream"| GCL
各组件职责
| 组件 | 所在文件 | 职责 |
|---|---|---|
BleConnectionManager | lib/manager/ble_connection_manager.dart | 连接能力对外的指令入口,全部为静态方法,仅负责把动作翻译成 MethodChannel 调用 |
BleBaseManager | lib/ble_base_manager.dart | 底层通道封装,提供 invokeMethod,是 Dart 与原生通信的唯一通道(本页通过引用确认其存在,具体实现见其源码) |
BleDeviceConnectionProcessor | lib/processor/ble_device_connection_processor.dart | 把 BleBaseEventProcessor 的原始事件转换为 Stream<String> / Stream<List<ScanDevice>> / Stream<DeviceConnection> |
BleBaseEventProcessor | lib/processor/ble_base_event_processor.dart | 事件总线过滤与取值工具,提供 filterByType 与 getValueFromEvent |
DeviceConnection | lib/model/device_connection.dart | 连接状态模型,核心字段 state(int),提供 fromMap/toMap 双向转换 |
ConnectionStateManager | example/lib/utils/connection_state_manager.dart | 应用层全局连接状态缓存,ChangeNotifier 单例,供 UI 监听刷新 |
DeviceConnectionManager.kt / DeviceConnection.java / DoubleConnectionSp.java | android/.../sdk/data/manager/ 等 | 原生侧连接管理、状态模型与双连接配置持久化(本页仅确认其存在与命名,具体实现属于原生 SDK 范围) |
设计意图
- 静态方法而非实例:
BleConnectionManager与BleDeviceConnectionProcessor全部使用静态成员,说明 SDK 采用全局单例式通道设计——蓝牙连接本质上是设备级(而非页面级)资源,不需要为每个调用者创建独立实例。 - Stream 而非回调:事件流天然支持多订阅者,扫描列表、连接状态可以被任意多个页面同时监听,且可以组合(
map、where、listen等操作符),比一次性回调更灵活。 - 状态集中缓存:
ConnectionStateManager在收到新状态时先做相等性判断再notifyListeners(),避免无意义的 UI 重建;同时提供静态currentState,让非 Widget 代码也能读取最新状态。
主内容:连接链路的实现剖析
1. 指令入口:BleConnectionManager
该管理器是 Dart 侧向原生发起连接动作的唯一入口,全部为静态 Future<void> 方法,内部统一委托给 BleBaseManager.invokeMethod:
/// Connect to device
static Future<void> connectDevice(int index) async {
await BleBaseManager.invokeMethod(
BleMethodConstants.methodConnectDevice,
arguments: {BleMethodConstants.argIndex: index},
);
}
/// Disconnect from device
static Future<void> disconnectBtDevice(int index) async {
await BleBaseManager.invokeMethod(
BleMethodConstants.methodDisconnectBtDevice,
arguments: {BleMethodConstants.argIndex: index},
);
}
Source: ble_connection_manager.dart
要点解读:
- 按索引寻址设备:
connectDevice(int index)/disconnectBtDevice(int index)均以扫描列表中的索引定位设备,而不是设备地址或 UUID。这意味着调用方必须先完成扫描并持有扫描列表(scanDeviceListStream),索引与设备的对应关系由调用方负责维护——这是 SDK 为简化跨端参数传递而做的取舍。 - 参数键集中管理:
argIndex等参数键与方法名一样集中在BleMethodConstants,避免字符串散落各处造成拼写不一致。 - 扫描控制:
startScan()/stopScan()无参数,扫描结果不在此处返回,而是通过scanStateStream/scanDeviceListStream事件流异步送达——指令与数据分离。
2. 连接状态模型:DeviceConnection
原生侧回传的每一个连接状态事件,都会被映射为 DeviceConnection 对象。模型极简,核心只有一个 state 字段:
factory DeviceConnection.fromMap(Map<dynamic, dynamic> map) {
assert(map.containsKey(_keyState),
'DeviceConnection map must contain state key');
return DeviceConnection(
state: map[_keyState] as int,
);
}
Source: device_connection.dart
要点解读:
assert前置校验:fromMap要求事件数据必须包含state键,否则在调试模式下立即失败。这是对「原生事件契约」的强约束——宁可快速失败,也不允许静默解析出脏数据。- 值语义相等:重写了
operator ==与hashCode,只要state相同即视为相等。这使Stream.distinct()或ChangeNotifier的去重判断可以直接工作。 state的语义:具体数值(如连接成功、失败、断开)由原生 SDK 定义,Dart 侧仅透传 int;example 工程中AppConstants.connectionFailed用作默认/失败兜底值。精确的状态枚举值定义在常量文件中,本页未展开。
3. 事件流处理:BleDeviceConnectionProcessor
该处理器把 BleBaseEventProcessor 提供的原始事件转换为三个类型安全的 Stream:
/// Device connection status stream
static Stream<DeviceConnection> get deviceConnectionStream {
return BleBaseEventProcessor.filterByType(BleEventConstants.typeDeviceConnection)
.map((event) {
final data = BleBaseEventProcessor.getValueFromEvent(event);
return DeviceConnection.fromMap(data);
});
}
三个 Stream 的语义与来源:
| Stream | 元素类型 | 过滤的事件类型 | 解析字段 |
|---|---|---|---|
scanStateStream | String | typeScanDeviceList | keyState(扫描进行中/停止等状态字符串) |
scanDeviceListStream | List<ScanDevice> | typeScanDeviceList | keyList(原始 Map 列表,逐个 ScanDevice.fromMap) |
deviceConnectionStream | DeviceConnection | typeDeviceConnection | 整个事件数据交给 DeviceConnection.fromMap |
设计要点:
- 静态 getter + 延迟求值:每次访问 getter 都基于
filterByType(...)返回的流重新.map,但底层事件源是同一个,因此多个页面订阅同一条流不会丢失事件,也不会重复触发原生逻辑。 - 容错解析:扫描列表解析时对
keyList缺失做了兜底(?? []),并只用whereType<Map>()过滤出合法的设备 Map,非法条目被静默跳过——原生数据与 Dart 类型系统之间的边界防御。 - 关注点分离:
BleDeviceConnectionProcessor只负责「转换」,不负责「分发」;分发由应用层决定(直接订阅或汇入ConnectionStateManager)。
4. 应用层全局状态:ConnectionStateManager
example 工程通过一个 ChangeNotifier 单例把「最新连接状态」集中管理,供全局 UI 监听:
class ConnectionStateManager extends ChangeNotifier {
static ConnectionStateManager? _instance;
ConnectionStateManager._internal();
factory ConnectionStateManager() {
_instance ??= ConnectionStateManager._internal();
return _instance!;
}
int _connectState = AppConstants.connectionFailed;
int get connectState => _connectState;
void updateConnectState(int newState) {
if (_connectState != newState) {
_connectState = newState;
notifyListeners();
}
}
static int get currentState => _instance?._connectState ?? AppConstants.connectionFailed;
}
Source: connection_state_manager.dart
要点解读:
- 单例工厂:私有构造 + 懒加载静态实例,保证全应用共享同一份连接状态;
currentState静态 getter 让非 Widget 代码(如业务服务)无需持有实例即可读取。 - 变更去重:
updateConnectState先比较新旧值,只有真正变化才notifyListeners()——这是对 Flutter 重建开销的主动优化。 - 默认失败态:初始状态为
AppConstants.connectionFailed,语义上「未连接即失败态」,UI 无需为 null 状态单独写分支。 - 配套监听器:同目录下的
global_connection_listener.dart负责把BleDeviceConnectionProcessor的事件流桥接进此类状态管理(本页通过文件清单确认其存在,具体桥接细节见其源码)。
核心流程:一次完整的连接生命周期
sequenceDiagram
participant UI as Flutter UI
participant CSM as ConnectionStateManager
participant BCM as BleConnectionManager
participant BBM as BleBaseManager
participant DCM as Android DeviceConnectionManager
participant BLE as BLE 协议栈
participant BEP as BleBaseEventProcessor
participant BCP as BleDeviceConnectionProcessor
UI->>BCM: startScan()
BCM->>BBM: invokeMethod(methodStartScan)
BBM->>DCM: MethodChannel 调用
DCM->>BLE: 启动扫描
BLE-->>DCM: 扫描结果/状态回调
DCM-->>BEP: 派发 typeScanDeviceList 事件
BEP-->>BCP: filterByType + getValueFromEvent
BCP-->>UI: scanStateStream / scanDeviceListStream
UI->>BCM: connectDevice(index)
BCM->>BBM: invokeMethod(methodConnectDevice, {argIndex: index})
BBM->>DCM: MethodChannel 调用
DCM->>BLE: 发起 GATT 连接
BLE-->>DCM: 连接结果回调
DCM-->>BEP: 派发 typeDeviceConnection 事件
BEP-->>BCP: 映射为 DeviceConnection
BCP-->>CSM: deviceConnectionStream 推送
CSM-->>UI: updateConnectState + notifyListeners() 刷新
UI->>BCM: disconnectBtDevice(index)
BCM->>BBM: invokeMethod(methodDisconnectBtDevice, {argIndex: index})
BBM->>DCM: MethodChannel 调用
DCM->>BLE: 断开 GATT 连接
状态流转决策图
flowchart TD
Start(["用户发起连接"]) --> Scan["startScan() 指令"]
Scan --> List["scanDeviceListStream 收到设备列表"]
List --> Pick["按 index 选择目标设备"]
Pick --> Connect["connectDevice(index) 指令"]
Connect --> Wait{"等待 deviceConnectionStream"}
Wait -->|"state 表示连接成功"| OK["ConnectionStateManager 更新成功态"]
Wait -->|"state 表示连接失败"| Fail["更新失败态<br/>(connectionFailed 兜底)"]
Wait -->|"state 表示已断开"| Disc["更新断开态"]
OK --> Data(["连接就绪,可收发数据"])
Fail --> Retry(["提示用户,可重新连接"])
Disc --> Retry
Data -->|"用户退出/异常"| Disc
流程要点
- 扫描与连接解耦:扫描指令(
startScan/stopScan)与连接指令(connectDevice/disconnectBtDevice)在BleConnectionManager中分属不同方法,中间通过事件流传递设备列表——这正是「指令下发、事件回报」双通道设计的体现。 - 索引是连接上下文:
connectDevice(index)的index必须与scanDeviceListStream中的列表位置一致;如果列表在两次事件之间发生变化,调用方需要自行保证索引有效性。 - 状态收敛到单例:无论原生层上报多少次状态,最终都通过
ConnectionStateManager.updateConnectState的相等性判断收敛为「最新一次有效状态」,UI 只对变化做出反应。
使用示例
示例一:发起扫描并订阅设备列表与连接状态
以下代码展示了「指令 + 事件流」的典型组合用法(片段来自处理器与管理器源码):
// 订阅扫描状态(String:扫描中/停止等)
static Stream<String> get scanStateStream {
return BleBaseEventProcessor.filterByType(BleEventConstants.typeScanDeviceList)
.map((event) {
final data = BleBaseEventProcessor.getValueFromEvent(event);
return data[BleEventConstants.keyState] as String? ?? '';
});
}
// 订阅连接状态,映射为 DeviceConnection
static Stream<DeviceConnection> get deviceConnectionStream {
return BleBaseEventProcessor.filterByType(BleEventConstants.typeDeviceConnection)
.map((event) {
final data = BleBaseEventProcessor.getValueFromEvent(event);
return DeviceConnection.fromMap(data);
});
}
调用方使用模式:
// 1. 订阅事件流(任意页面/任意次数,互不干扰)
BleDeviceConnectionProcessor.deviceConnectionStream.listen((conn) {
ConnectionStateManager().updateConnectState(conn.state);
});
// 2. 下发指令
await BleConnectionManager.startScan();
// ... 收到 scanDeviceListStream 后展示列表 ...
await BleConnectionManager.connectDevice(index);
Source: ble_connection_manager.dart
示例二:监听全局连接状态驱动 UI
example 工程中所有需要感知连接状态的页面,只需监听 ConnectionStateManager:
final _connectionStateManager = ConnectionStateManager();
void initState() {
super.initState();
_connectionStateManager.addListener(_onConnectionStateChanged);
}
void _onConnectionStateChanged() {
final state = _connectionStateManager.connectState;
// 根据 state 刷新连接指示、按钮可用性等
setState(() {});
}
Source: connection_state_manager.dart
说明:
updateConnectState只在状态真正变化时触发notifyListeners(),因此上面的监听回调天然避免了无效重建。
示例三:解析原生连接状态事件
DeviceConnection.fromMap 是原生事件进入 Dart 类型系统的唯一入口,调试时可借助 toString 直接打印:
final conn = DeviceConnection.fromMap({'state': 0x100});
print(conn); // DeviceConnection(state: 256)
// 相等性基于 state 值语义
assert(conn == DeviceConnection(state: 0x100));
assert(conn.hashCode == DeviceConnection(state: 0x100).hashCode);
Source: device_connection.dart
配置选项
本能力的「配置面」由常量类与原生侧持久化共同构成。Dart 侧方法名、参数键、事件类型集中在常量类中,改动一处即可全局生效;原生侧的双连接配置通过 SharedPreferences 持久化。
Dart 侧常量(配置面)
| 常量类 | 关键成员 | 类型 | 用途 |
|---|---|---|---|
BleMethodConstants | methodStartScan / methodStopScan | String | MethodChannel 方法名:开始/停止扫描 |
BleMethodConstants | methodConnectDevice / methodDisconnectBtDevice | String | MethodChannel 方法名:连接/断开设备 |
BleMethodConstants | argIndex | String | 设备索引参数键,随连接/断开指令传递 |
BleEventConstants | typeScanDeviceList / typeDeviceConnection | String | 事件类型:扫描列表事件 / 连接状态事件 |
BleEventConstants | keyState / keyList | String | 事件数据字段键:状态字符串 / 设备列表 |
AppConstants | connectionFailed | int | example 应用默认/兜底连接状态值 |
注:具体常量取值定义于各常量文件(
lib/constant/ble_method_constants.dart、lib/constant/ble_event_constants.dart、lib/constant/constants.dart),本页未逐一读取,取值以源码为准。
原生侧配置:双连接持久化
Android 原生层通过 DoubleConnectionSp.java 持久化双连接(同时连接两台设备)的相关配置,例如上次连接过的设备索引/地址,供下次启动恢复会话使用。具体键名与默认值见原生源码:
Source: DoubleConnectionSp.java
API 参考
BleConnectionManager(静态类,lib/manager/ble_connection_manager.dart)
static Future<void> startScan()
开始扫描周围蓝牙设备。无参数;扫描结果不在此返回,通过 scanDeviceListStream 异步推送。
static Future<void> stopScan()
停止扫描。通常在拿到目标设备列表或页面退出时调用,以节省电量与信道资源。
static Future<void> connectDevice(int index)
按扫描列表索引连接设备。
参数:
index(int):目标设备在扫描列表中的索引。
说明: 索引必须与 scanDeviceListStream 最近一次推送的列表位置一致;连接结果通过 deviceConnectionStream 异步回报,本方法只保证指令送达原生层。
static Future<void> disconnectBtDevice(int index)
断开指定索引设备的连接。
参数:
index(int):目标设备在扫描列表中的索引。
异常行为: 各方法均 await BleBaseManager.invokeMethod,若 MethodChannel 尚未初始化或原生调用失败,异常沿 Future 向上抛出,由调用方处理。
DeviceConnection(lib/model/device_connection.dart)
| 成员 | 签名 | 说明 |
|---|---|---|
| 构造 | const DeviceConnection({required int state}) | 仅需 state 字段 |
| 属性 | final int state | 原生连接状态值(语义由原生 SDK 定义) |
| 工厂 | factory DeviceConnection.fromMap(Map<dynamic, dynamic> map) | 从事件数据解析;assert 要求必须含 state 键 |
| 方法 | Map<String, dynamic> toMap() | 序列化为 {'state': state} |
| 重写 | operator == / hashCode | 值语义相等,仅比较 state |
| 重写 | String toString() | 输出 DeviceConnection(state: N) 便于调试 |
BleDeviceConnectionProcessor(静态类,lib/processor/ble_device_connection_processor.dart)
| 成员 | 签名 | 说明 |
|---|---|---|
scanStateStream | static Stream<String> | 扫描状态流(基于 typeScanDeviceList 事件的 keyState 字段) |
scanDeviceListStream | static Stream<List<ScanDevice>> | 扫描设备列表流(keyList 字段逐个映射为 ScanDevice) |
deviceConnectionStream | static Stream<DeviceConnection> | 设备连接状态流(typeDeviceConnection 事件映射为 DeviceConnection) |
ConnectionStateManager(example/lib/utils/connection_state_manager.dart)
| 成员 | 签名 | 说明 |
|---|---|---|
| 工厂 | factory ConnectionStateManager() | 懒加载单例;私有 _internal 构造 |
| 属性 | int get connectState | 当前连接状态,初始为 AppConstants.connectionFailed |
| 方法 | void updateConnectState(int newState) | 更新状态;值未变化时不通知 |
| 静态 | static int get currentState | 无实例也可读取最新状态;未初始化时返回 connectionFailed |
失败模式、边界情况与并发
事件契约被破坏(缺少 state 键)
DeviceConnection.fromMap 通过 assert 强制要求事件数据包含 state 键。在调试模式(assert 生效)下,原生事件契约被破坏会立即抛错,帮助开发者第一时间发现两端协议不一致;但在发布模式下 assert 会被剥离,缺失键将导致 map[_keyState] as int 对 null 做类型转换而抛出 TypeError。设计意图:宁可快速失败,也不允许把脏数据悄悄带入业务层。
扫描列表索引漂移
connectDevice(index) / disconnectBtDevice(index) 依赖调用方维护的索引。若在两次 scanDeviceListStream 推送之间列表顺序变化(设备移出范围、去重合并等),旧索引可能指向错误设备。调用方应在连接前重新确认索引,或在 UI 上禁用「已过期」的列表项。
重复订阅与流生命周期
scanDeviceListStream 等 getter 每次访问都会基于同一事件源重新构造映射流。如果页面在 dispose 时未取消订阅(StreamSubscription.cancel()),会产生泄漏的监听器。此外,多页面同时订阅时,每个订阅者都会独立收到事件——这是 Stream 多播语义的预期行为,但业务上需要注意幂等处理(例如重复的「连接成功」状态不应触发重复逻辑,ConnectionStateManager 的相等性判断恰好提供了这层保护)。
并发与单例线程安全
ConnectionStateManager是应用内单例,所有更新都发生在 Dart 单线程事件循环中,updateConnectState的「读-比较-写」不存在跨线程竞争;但要注意调用顺序:多个来源(页面 A、页面 B、后台服务)都可能调用updateConnectState,最后一个调用者决定最终状态,这与「全局最新状态」的语义一致。- 原生侧
DeviceConnectionManager与DoubleConnectionSp属于 SDK 线程模型(连接状态回调通常来自 BLE 回调线程),由原生层负责串行化后再通过事件通道回传 Dart;Dart 侧无需加锁。
连接失败与重试
初始状态即 connectionFailed,连接失败/断开后状态回落到失败或断开值。SDK 不提供自动重连,重试策略由应用层决定(重新 connectDevice(index))。DoubleConnectionSp 的双连接配置可在应用重启后恢复上次会话,属于原生侧的会话恢复机制。
性能与运维注意
- 避免过度重建:
ConnectionStateManager.updateConnectState的相等性短路保证了只有状态真正变化才触发notifyListeners,配合ChangeNotifier的监听方(如AnimatedBuilder/ListenableBuilder)可将重建范围限制在依赖该状态的子树。 - 扫描功耗:
startScan/stopScan是显式成对调用,长时间保持扫描会持续占用 BLE 广播信道与电量;建议页面在拿到目标设备后立即stopScan()。 - 事件解析成本:
scanDeviceListStream每次事件都会把整个列表重新ScanDevice.fromMap。设备数量大时(数十台以上)应避免在 UI 构建路径中同步做繁重处理,必要时在map之后追加where/distinct收窄。 - 双连接资源:原生侧支持双连接(
DoubleConnectionSp),同时维持两条 GATT 链路会占用更多系统资源,应用中需管理好每个连接的索引与状态,避免互相覆盖ConnectionStateManager的全局状态。
扩展点
- 自定义连接状态机:
DeviceConnection.state是 int 透传,可在应用层定义自己的状态枚举并映射原生值,例如把成功/失败/断开/连接中映射为业务状态,保持 UI 与 SDK 数值解耦。 - 多页面状态分发:除了
ConnectionStateManager(最新值缓存),也可直接订阅deviceConnectionStream做流式分发(如路由到多个页面的独立状态模块),两者可共存。 - 指令扩展:新增连接相关操作(如获取连接参数、查询已连接设备列表)时,按
BleConnectionManager的模式在BleMethodConstants增加方法名与参数键,再补一个静态方法即可,无需改动事件流结构。 - 重连策略:在
deviceConnectionStream上组合retryWhen/timeout等 Rx 式操作(或手写重试逻辑),即可在不改 SDK 的前提下实现自动重连。
测试情况
本页覆盖的源码未发现针对连接管理器的独立测试文件(搜索范围限于连接相关文件)。事件流与模型的纯函数特性(fromMap/toMap/==/Stream 映射)适合用 Dart 单元测试覆盖:构造原生格式的 Map,验证 DeviceConnection 解析、scanDeviceListStream 的列表映射与容错(非法条目被 whereType 过滤)以及 ConnectionStateManager 的去重通知行为。assert 路径可在调试模式下用缺失 state 键的 Map 验证契约校验。
相关链接
- BleConnectionManager(lib/manager/ble_connection_manager.dart)
- BleDeviceConnectionProcessor(lib/processor/ble_device_connection_processor.dart)
- DeviceConnection 模型(lib/model/device_connection.dart)
- ConnectionStateManager(example/lib/utils/connection_state_manager.dart)
- GlobalConnectionListener(example/lib/utils/global_connection_listener.dart)
- Android 原生 DeviceConnectionManager.kt
- Android 原生 DoubleConnectionSp.java
- SDK 发布版连接管理器(libs/Send Interface/ble_connection_manager.dart)
兄弟页面指引:设备扫描与发现流程、设备能力服务(音频/通话/文件)、OTA 升级等话题请参见对应目录页;本页仅聚焦连接指令、状态模型、事件流与应用层状态管理。