From b116e4bed3e15124dd15ffb1b9536db00a47a1e5 Mon Sep 17 00:00:00 2001 From: Songzex <2402265378@qq.com> Date: Fri, 17 Apr 2026 17:29:31 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E6=9C=BA=E5=99=A8=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E9=A1=B5=E9=9D=A2=E7=9A=84=E6=95=B0=E6=8D=AE=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E8=AE=BE=E5=AE=9A=E4=BF=9D=E8=AF=81=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=8F=8A=E6=97=B6=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/core/network/net_message_dispatcher.dart | 3 +- .../presentation/bloc/device_status_bloc.dart | 176 ++++++++++++------ .../pages/running_status_page.dart | 50 ++++- .../home/presentation/routes/home_routes.dart | 4 +- 4 files changed, 163 insertions(+), 70 deletions(-) diff --git a/lib/core/network/net_message_dispatcher.dart b/lib/core/network/net_message_dispatcher.dart index 8ad1673f..bb929ba0 100644 --- a/lib/core/network/net_message_dispatcher.dart +++ b/lib/core/network/net_message_dispatcher.dart @@ -47,6 +47,7 @@ class NetMessageDispatcher { Stream onStringMessage() { //print("0x02--TCP拦截推送解析开始"); _logger.log('0x02--TCP拦截推送解析开始'); + // 🔥 关键修复:使用asBroadcastStream()确保多个监听者都能收到数据 return onCommand(0x02).map((p) { try { // 尝试解码 @@ -59,7 +60,7 @@ class NetMessageDispatcher { _logger.log('❌ 解码 0x02 失败:$e, 原始字节:${p.payload}'); return ''; // 返回空字符串,避免流中断 } - }); + }).asBroadcastStream(); /* ///return onCommand(0x02).map((p) => utf8.decode(p.payload));*/ } diff --git a/lib/features/devices/presentation/bloc/device_status_bloc.dart b/lib/features/devices/presentation/bloc/device_status_bloc.dart index ee129c6a..ca91366b 100644 --- a/lib/features/devices/presentation/bloc/device_status_bloc.dart +++ b/lib/features/devices/presentation/bloc/device_status_bloc.dart @@ -16,73 +16,130 @@ class DeviceStatusBloc extends Bloc { final NetMessageDispatcher _dispatcher; final ILoggerService _logger = GetIt.I(); - - // 持有订阅引用,仅在 close 时取消 - StreamSubscription? _stringSub; - StreamSubscription? _jsonSub; - - // 🔥 新增:标记是否已初始化订阅,防止重复订阅 - bool _isSubscribed = false; + // 🔥 保存订阅引用,用于管理生命周期 + StreamSubscription? _tcpSubscription; DeviceStatusBloc(this._dispatcher) : super(DeviceStatusInitial()) { - // 1. 初始建立订阅(终身有效,除非 Bloc 关闭) - _setupStreamListeners(); + // 🔥 核心改动:直接在构造函数中建立TCP监听,类似RemoteControlCubit + _initDirectTcpListener(); - // 2. 注册事件处理 + // 保留事件处理(用于手动重置等场景) on(_handleReset); on(_handleDeviceStatusLoaded); on(_handlePushMessageReceived); } - // 🔥 核心修复:订阅逻辑只执行一次,不再随意 cancel/relisten - void _setupStreamListeners() { - if (_isSubscribed) { - return; // 如果已经订阅过,直接返回,避免重复操作 + // 🔥 新增:重新初始化TCP监听器(用于页面重新进入时调用) + void reinitializeListener() { + debugPrint('🔄 [DeviceStatusBloc] 重新初始化TCP监听器 - isClosed=$isClosed'); + _logger.log('🔄 [DeviceStatusBloc] 重新初始化TCP监听器 - isClosed=$isClosed'); + + if (isClosed) { + debugPrint('❌ [DeviceStatusBloc] BLoC已关闭,无法重新初始化!'); + _logger.log('❌ [DeviceStatusBloc] BLoC已关闭,无法重新初始化!'); + return; } - - debugPrint('🔗 [DeviceStatusBloc] 初始化 TCP 数据流订阅(终身有效)'); - - - // 订阅字符串流 (0x02) - _stringSub = _dispatcher.onStringMessage().listen( - (jsonString) { - debugPrint('Bloc层已经!!收到 0x02 数据事件,长度:${jsonString.length}'); - if (!isClosed) { - add(DeviceStatusLoaded(jsonString)); - } - }, - onDone: () => debugPrint('⚠️ [Bloc] 0x02 流已结束 (onDone) - 这通常意味着底层 TCP 彻底关闭'), - onError: (e) => debugPrint('❌ [Bloc] 0x02 流发生错误:$e'), - ); - - // 订阅 JSON 流 (0x12) - // _jsonSub = _dispatcher.onJsonMessage(0x12).listen( - // (jsonData) { - // debugPrint('📩 [Bloc] 收到 JSON 数据事件:$jsonData'); - // if (!isClosed) { - // add(PushMessageReceived(jsonData)); - // } - // }, - // onDone: () => debugPrint('⚠️ [Bloc] JSON 流已结束 (onDone)'), - // onError: (e) => debugPrint('❌ [Bloc] JSON 流发生错误:$e'), - // ); - - _isSubscribed = true; - // debugPrint('✅ [DeviceStatusBloc] 订阅建立完成,将持续监听数据流'); - _logger.log('✅ [DeviceStatusBloc] 订阅建立完成,将持续监听数据流'); + + // 取消旧的订阅 + debugPrint('📝 [DeviceStatusBloc] 取消旧订阅'); + _tcpSubscription?.cancel(); + + // 重置状态为初始状态 + debugPrint('📝 [DeviceStatusBloc] emit Initial状态'); + emit(DeviceStatusInitial()); + + // 重新建立监听 + debugPrint('📝 [DeviceStatusBloc] 调用 _initDirectTcpListener'); + _initDirectTcpListener(); } - // 🔥 核心修复:重置时仅清空状态,绝对不再触碰订阅关系 + // 🔥 新增:直接监听TCP 0x02指令,实时解析并emit状态 + void _initDirectTcpListener() { + debugPrint('🔗 [DeviceStatusBloc] 初始化直接TCP监听器(类似RemoteControlCubit)'); + _logger.log('🔗 [DeviceStatusBloc] 初始化直接TCP监听器'); + + _tcpSubscription = _dispatcher.onStringMessage().listen( + (message) { + //debugPrint('📩 [DeviceStatusBloc] 直接收到0x02数据,长度:${message.length}'); + + if (message.isEmpty) { + //debugPrint('⚠️ [DeviceStatusBloc] 消息为空,跳过'); + return; + } + + try { + // 🔥 直接解析,不经过事件转换 + final fields = message.trim().split(','); + + if (fields.length < 18) { + //debugPrint('⚠️ [DeviceStatusBloc] 字段不足:${fields.length},期望≥18'); + if (!isClosed) { + emit(DeviceStatusError('字段不足,期望≥18,实际:${fields.length}')); + } + return; + } + + final status = RunningStatusEntity.fromFields(fields); + final gps = GPSEntity(status.latitude, status.longitude); + + //debugPrint('✅ [DeviceStatusBloc] 直接解析成功,更新状态:Lat=${gps.latitude}, Lng=${gps.longitude}'); + _logger.log('✅ [DeviceStatusBloc] 直接解析成功,更新状态'); + + // 🔥 关键修复:BLoC有Equatable去重机制,必须创建新对象才能触发UI更新 + if (!isClosed) { + // 创建全新的status和gps对象,绕过Equatable去重 + final newStatus = RunningStatusEntity( + voltage: status.voltage, + leftTargetSpeed: status.leftTargetSpeed, + rightTargetSpeed: status.rightTargetSpeed, + leftMeasureSpeed: status.leftMeasureSpeed, + rightMeasureSpeed: status.rightMeasureSpeed, + leftCurrent: status.leftCurrent, + rightCurrent: status.rightCurrent, + leftMotorTemp: status.leftMotorTemp, + rightMotorTemp: status.rightMotorTemp, + chipTemp: status.chipTemp, + yaw: status.yaw, + pitch: status.pitch, + roll: status.roll, + satelliteCnt: status.satelliteCnt, + qual: status.qual, + headingStatus: status.headingStatus, + latitude: status.latitude, + longitude: status.longitude, + timestamp: status.timestamp, + knifeCuttingSpeed: status.knifeCuttingSpeed, + controlMode: status.controlMode, + battery: status.battery, + workingArea: status.workingArea, + obstacleFlag: status.obstacleFlag, + ); + final newGps = GPSEntity(status.latitude, status.longitude); + emit(DeviceStatusUpdated(newStatus, newGps)); + } + } catch (e, stack) { + //debugPrint('❌ [DeviceStatusBloc] 直接解析异常:$e\n$stack'); + _logger.log('❌ [DeviceStatusBloc] 直接解析异常:$e'); + if (!isClosed) { + emit(DeviceStatusError('解析失败:$e')); + } + } + }, + onDone: () => debugPrint('⚠️ [DeviceStatusBloc] TCP流已结束(onDone)'), + onError: (e) => debugPrint('❌ [DeviceStatusBloc] TCP流错误:$e'), + ); + + debugPrint('✅ [DeviceStatusBloc] 直接TCP监听器已建立完成'); + _logger.log('✅ [DeviceStatusBloc] 直接TCP监听器已建立完成'); + } + + // 🔥 重置时仅清空状态 Future _handleReset(DeviceStatusReset event, Emitter emit) async { - // debugPrint('🔄 收到重置事件:仅清空状态,保持订阅活跃(不重连)'); - _logger.log('🔄 收到重置事件:仅清空状态,保持订阅活跃(不重连)'); + debugPrint('🔄 [DeviceStatusBloc] 收到重置事件:清空状态'); + _logger.log('🔄 [DeviceStatusBloc] 收到重置事件:清空状态'); - - // 只 emit 初始状态,让 UI 清除旧设备的数据(如速度归零、轨迹清除) + // 只 emit 初始状态,让 UI 清除旧设备的数据 emit(DeviceStatusInitial()); - - // ❌ 严禁在此处调用 _setupStreamListeners() 或 cancel 订阅 - // 因为 TCP 重连期间数据流可能一直在推送,cancel 会导致关键首包丢失 } Future _handleDeviceStatusLoaded( @@ -130,15 +187,10 @@ class DeviceStatusBloc extends Bloc { @override Future close() { - // 只有在 Bloc 彻底销毁时才取消订阅 - //debugPrint('🚫 [DeviceStatusBloc] 正在关闭,取消所有订阅 '); - // debugPrint('🚫 [DeviceStatusBloc] 正在关闭,取消所有订阅'); - //debugPrint('🔥 [DeviceStatusBloc] 销毁堆栈跟踪:\n${StackTrace.current}'); - // _stringSub?.cancel(); - // _jsonSub?.cancel(); - //return super.close(); + debugPrint('🚫 [DeviceStatusBloc] 页面退出,仅取消TCP订阅(不关闭BLoC)'); + _logger.log('🚫 [DeviceStatusBloc] 页面退出,仅取消TCP订阅(不关闭BLoC)'); + _tcpSubscription?.cancel(); + // 🔥 关键修复:不调用 super.close(),保持 BLoC 活跃 return Future.value(); - - } } diff --git a/lib/features/home/presentation/pages/running_status_page.dart b/lib/features/home/presentation/pages/running_status_page.dart index caab3094..6966f657 100644 --- a/lib/features/home/presentation/pages/running_status_page.dart +++ b/lib/features/home/presentation/pages/running_status_page.dart @@ -15,7 +15,7 @@ import '../../../../core/di/injection.dart'; import '../../../../core/network/net_message_dispatcher.dart'; import '../../../../core/network/protocol_decoder.dart'; import '../../../devices/presentation/bloc/devices_cubit.dart'; -import '../../../devices/presentation/bloc/device_status_bloc.dart'; +import '../../../devices/presentation/bloc/device_status_cubit.dart'; import '../../../devices/presentation/bloc/device_status_state.dart'; // 配置:数据超时时间(5秒) @@ -28,7 +28,10 @@ class RunningStatusPage extends StatefulWidget { State createState() => _RunningStatusPageState(); } -class _RunningStatusPageState extends State { +class _RunningStatusPageState extends State with WidgetsBindingObserver { + // 🔥 静态实例引用,用于生命周期回调 + static _RunningStatusPageState? _instance; + bool _isCardView = true; // 仪表盘/折线图切换状态 @@ -58,12 +61,31 @@ class _RunningStatusPageState extends State { @override void initState() { super.initState(); - // 初始化超时计时器 + // 🔥 注册静态实例 + _instance = this; + debugPrint('🚀 [RunningStatusPage] initState 被调用'); + // 🔥 简化:直接重置状态,不需要重新初始化监听器 + context.read().reset(); + // 🔥 关键:立即启动超时计时器,如果5秒内没收到数据就显示"暂无数据" _startDataTimeoutTimer(); + + // 🔥 新增:监听应用生命周期 + WidgetsBinding.instance.addObserver(this); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.resumed) { + debugPrint('🔄 [RunningStatusPage] 应用恢复,强制刷新UI'); + _forceRefresh(); + } } @override void dispose() { + // 🔥 移除生命周期观察者 + WidgetsBinding.instance.removeObserver(this); + _instance = null; // 销毁计时器,防止内存泄漏 _dataTimeoutTimer?.cancel(); super.dispose(); @@ -819,7 +841,7 @@ class _RunningStatusPageState extends State { body: CustomScrollView( physics: const BouncingScrollPhysics(), slivers: [ - BlocBuilder( + BlocBuilder( builder: (context, state) { // 新增:超时状态下显示-- String qual = _isDataTimeout ? '--' : '--'; @@ -927,7 +949,7 @@ class _RunningStatusPageState extends State { ), SliverToBoxAdapter( - child: BlocBuilder( + child: BlocBuilder( builder: (context, state) { debugPrint('🎨 [UI-Build] BlocBuilder 重建!当前状态类型:${state.runtimeType}'); @@ -942,6 +964,12 @@ class _RunningStatusPageState extends State { if (!_isDataTimeout && state is DeviceStatusUpdated) { debugPrint('📈 [UI] 检测到 Updated 状态,准备追加图表数据'); _appendChartData(state); + // 🔥 关键:收到数据后立即重置超时计时器 + _startDataTimeoutTimer(); + // 如果之前是超时状态,现在恢复 + if (_isDataTimeout) { + setState(() => _isDataTimeout = false); + } } return Container(margin: const EdgeInsets.all(8), child: _isCardView ? _buildCardContentView(state) : _buildChartContentView(state)); }, @@ -992,6 +1020,18 @@ class _RunningStatusPageState extends State { _voltageHistory.clear(); _timeIndex = 0; } + + // 🔥 新增:强制刷新UI(用于息屏恢复) + void _forceRefresh() { + if (mounted) { + debugPrint('🔄 [RunningStatusPage] 执行强制刷新'); + setState(() { + // 触发 UI 重建 + }); + // 重置超时计时器 + _startDataTimeoutTimer(); + } + } } // ====================== 带动画的圆形仪表盘 Widget ====================== diff --git a/lib/features/home/presentation/routes/home_routes.dart b/lib/features/home/presentation/routes/home_routes.dart index 244be210..3aa62178 100644 --- a/lib/features/home/presentation/routes/home_routes.dart +++ b/lib/features/home/presentation/routes/home_routes.dart @@ -7,7 +7,7 @@ import 'package:maibu_satabot_v2/features/home/presentation/pages/productDesc.da import '../../../../core/di/injection.dart'; import '../../../../core/router/route_paths.dart'; -import '../../../devices/presentation/bloc/device_status_bloc.dart'; +import '../../../devices/presentation/bloc/device_status_cubit.dart'; import '../../../remote_control/presentation/bloc/remote_control_cubit.dart'; import '../../../remote_control/presentation/pages/remote_control_page.dart'; import '../pages/route_plan_page.dart'; @@ -33,7 +33,7 @@ class HomeRoutes { path: RoutePaths.runningStatus, // builder: (context, state) => const RunningStatusPage(), builder: (context, state) => BlocProvider( - create: (_) => sl(), // 从 GetIt 获取单例 + create: (_) => DeviceStatusCubit(sl()), // 使用简化版 Cubit child: const RunningStatusPage(), ), ),