一、问题背景
在 Flutter App 中集成 BLE 设备(如血氧仪)后,常见问题:
- 页面切换(Get.to / Get.back)
- 实际蓝牙 物理连接仍存在
- 但 UI 显示:
- 未连接
- 数据归零
- 波形停止
- 需要重新连接才能恢复
二、根因总结(核心)
BLE 状态被绑定到了页面 / Controller 生命周期
当页面 pop 或 Controller dispose:
- characteristic 被释放
- notify 监听被 cancel
- Rx 状态被重置
👉 导致 “逻辑断连”,而不是“物理断连”
三、核心设计原则(务必牢记)
- BLE = 全局资源
- 页面 / Controller = 短生命周期
- 任何 BLE 状态,都不能放在页面层
四、最终解决方案
架构分层
BLE Service(单例 / 常驻)
├─ connect / disconnect
├─ characteristic / notify
├─ 数据解析
├─ StreamController.broadcast
├─ 连接状态快照
└─ 最新数据快照
Controller(页面级)
├─ 只维护 UI Rx 状态
├─ 订阅 Service Stream
└─ 从 Service 恢复快照
UI Page
└─ 纯展示(Obx)
五、关键实现要点
1️⃣ BLE Service 必须是单例
class BleService {
static final BleService _instance = BleService._internal();
factory BleService() => _instance;
BleService._internal();
}
2️⃣ 所有 Stream 必须 broadcast
final StreamController<T> _ctrl =
StreamController<T>.broadcast();
原因:
- 多页面
- 多 controller
- 页面销毁不影响数据源
3️⃣ 连接状态 & 快照放在 Service
bool _connected;
String _deviceId;
String _deviceName;
Stream<bool> get connectionStream;
Pc60fOxiData? latestOxi;
4️⃣ Controller onInit 无感恢复
void onInit() {
connected.value = ble.isConnected;
deviceName.value = ble.connectedDeviceName;
final latest = ble.latestOxi;
if (latest != null) {
_onData(latest);
}
_connSub = ble.connectionStream.listen((v) {
connected.value = v;
});
}
5️⃣ 页面切换不影响 BLE
- Controller 可销毁
- Service 常驻
- 页面回来 → 立刻恢复 UI
六、波形“贴底直线”的真实原因
- 协议波形值:0 ~ 127
- Controller 误归一化为:0 ~ 1
- UI 按 0~127 绘制
👉 数值尺度不匹配
正确做法
waveformPoints.add(p.value.toDouble());
七、自动历史记录的节流策略
目标
- 不高频写 Hive
- 关键变化不丢
条件
- 时间 ≥ 5 秒
- 或 SpO₂ ≥ ±2%
- 或 PR ≥ ±5 bpm
八、历史记录职责划分
| 方法 | 职责 |
|---|---|
_appendHistory_o | 自动记录(后台、节流) |
saveCurrentToHistory | 用户手动保存 |
_saveRecord | 统一写 Hive |
九、总结
页面切换后数据丢失,不是 BLE 断了,
而是 BLE 状态放错了层级。
正确做法:
BLE 常驻,Controller 可销毁,UI 只展示。
十、BLE 项目模板骨架
📁 目录结构推荐
lib/
└─ ble/
├─ ble_service.dart // 单例,BLE 核心
├─ ble_models.dart // 数据模型
├─ ble_parser.dart // 协议解析
└─ ble_constants.dart
└─ feature_x/
├─ controller.dart // UI Controller
├─ page_home.dart
├─ page_connect.dart
└─ page_history.dart
1️⃣ BLE Service 模板(核心)
class BleService {
static final BleService _i = BleService._internal();
factory BleService() => _i;
BleService._internal();
bool _connected = false;
String _deviceId = '';
String _deviceName = '';
final StreamController<bool> _connCtrl =
StreamController<bool>.broadcast();
bool get isConnected => _connected;
Stream<bool> get connectionStream => _connCtrl.stream;
Future<void> connect(...) async {
// connect + discover + notify
_connected = true;
_connCtrl.add(true);
}
Future<void> disconnect() async {
_connected = false;
_connCtrl.add(false);
}
}
2️⃣ Controller 模板(UI 层)
class FeatureController extends GetxController {
final ble = BleService();
final connected = false.obs;
StreamSubscription<bool>? _connSub;
void onInit() {
connected.value = ble.isConnected;
_connSub = ble.connectionStream.listen((v) {
connected.value = v;
});
}
void onClose() {
_connSub?.cancel();
super.onClose();
}
}
3️⃣ UI Page 模板
Widget build(BuildContext context) {
final c = Get.find<FeatureController>();
return Obx(() {
if (!c.connected.value) {
return const Text('未连接');
}
return const Text('已连接');
});
}
4️⃣ 永久避免问题的 Checklist
- BLE Service 是否单例?
- notify 是否只在 Service?
- Stream 是否 broadcast?
- Controller 是否只做 UI?
- 是否有快照(latestData)?
- 页面切换是否不影响 BLE?
✅ 这个模版解决的问题:
- 页面随便切,BLE 不掉
- Controller 随便销毁,状态不丢
- UI 秒恢复
- 波形 / 数据 / 历史稳定
166

被折叠的 条评论
为什么被折叠?



