Files
flutterApp/lib/features/devices/presentation/bloc/device_status_bloc.dart
Songzex 9f3a7e578a 完成优化登录的优化
完成切换和切回APP数据的及时响应
完成服务下发退出账号的命令
2026-03-09 13:21:18 +08:00

133 lines
4.9 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:async';
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:maibu_satabot_v2/features/devices/domain/entities/running_status_entity.dart';
import 'package:maibu_satabot_v2/features/devices/domain/entities/gps_entity.dart';
import 'package:maibu_satabot_v2/core/network/protocol_decoder.dart';
import '../../../../core/network/net_message_dispatcher.dart';
import 'device_status_event.dart';
import 'device_status_state.dart';
class DeviceStatusBloc extends Bloc<DeviceStatusEvent, DeviceStatusState> {
final NetMessageDispatcher _dispatcher;
// 持有订阅引用,仅在 close 时取消
StreamSubscription? _stringSub;
StreamSubscription? _jsonSub;
// 🔥 新增:标记是否已初始化订阅,防止重复订阅
bool _isSubscribed = false;
DeviceStatusBloc(this._dispatcher) : super(DeviceStatusInitial()) {
// 1. 初始建立订阅(终身有效,除非 Bloc 关闭)
_setupStreamListeners();
// 2. 注册事件处理
on<DeviceStatusReset>(_handleReset);
on<DeviceStatusLoaded>(_handleDeviceStatusLoaded);
on<PushMessageReceived>(_handlePushMessageReceived);
}
// 🔥 核心修复:订阅逻辑只执行一次,不再随意 cancel/relisten
void _setupStreamListeners() {
if (_isSubscribed) {
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] 订阅建立完成,将持续监听数据流');
}
// 🔥 核心修复:重置时仅清空状态,绝对不再触碰订阅关系
Future<void> _handleReset(DeviceStatusReset event, Emitter<DeviceStatusState> emit) async {
debugPrint('🔄 收到重置事件:仅清空状态,保持订阅活跃(不重连)');
// 只 emit 初始状态,让 UI 清除旧设备的数据(如速度归零、轨迹清除)
emit(DeviceStatusInitial());
// ❌ 严禁在此处调用 _setupStreamListeners() 或 cancel 订阅
// 因为 TCP 重连期间数据流可能一直在推送,cancel 会导致关键首包丢失
}
Future<void> _handleDeviceStatusLoaded(
DeviceStatusLoaded event,
Emitter<DeviceStatusState> emit,
) async {
try {
debugPrint('🔍 开始解析数据:${event.jsonString}');
final fields = event.jsonString.trim().split(',');
if (fields.length < 18) {
debugPrint('⚠️ 字段不足:${fields.length}');
emit(DeviceStatusError('字段不足,期望 ≥18,实际:${fields.length}'));
return;
}
final status = RunningStatusEntity.fromFields(fields);
final gps = GPSEntity(status.latitude, status.longitude);
debugPrint('✅ 解析成功,更新状态:Lat=${gps.latitude}, Lng=${gps.longitude}');
emit(DeviceStatusUpdated(status, gps));
} catch (e, stack) {
debugPrint('❌ 解析异常:$e\n$stack');
emit(DeviceStatusError('解析失败:$e'));
}
}
Future<void> _handlePushMessageReceived(
PushMessageReceived event,
Emitter<DeviceStatusState> emit,
) async {
try {
final eventStr = event.jsonData['event'] ?? '';
final deviceId = event.jsonData['deviceId'] ?? '未知';
debugPrint('收到推送事件:$eventStr, 设备:$deviceId');
// 这里可以根据需要 emit 新状态
} catch (e) {
emit(DeviceStatusError('解析推送消息失败:$e'));
}
}
@override
Future<void> close() {
// 只有在 Bloc 彻底销毁时才取消订阅
//debugPrint('🚫 [DeviceStatusBloc] 正在关闭,取消所有订阅 ');
// debugPrint('🚫 [DeviceStatusBloc] 正在关闭,取消所有订阅');
//debugPrint('🔥 [DeviceStatusBloc] 销毁堆栈跟踪:\n${StackTrace.current}');
// _stringSub?.cancel();
// _jsonSub?.cancel();
//return super.close();
return Future.value();
}
}