优化配置,修复 demo bug
1,network 框架完善 2,websocket 机制完善 3,设计文档整理到架构文档 4,脚本,配置完善
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
|
||||
import 'package:networks_sdk/networks_sdk.dart';
|
||||
|
||||
import 'network_backoff_debouncer.dart';
|
||||
@@ -10,9 +9,8 @@ import 'network_backoff_debouncer.dart';
|
||||
/// 参考 HTTP 层 onTokenRefresh 的回调注入模式。
|
||||
/// App 层在 Provider 装配时注入解密/解析逻辑,
|
||||
/// 不在 SDK 内部调用加解密 SDK。
|
||||
typedef MessageTransformer = Map<String, dynamic> Function(
|
||||
Map<String, dynamic> raw,
|
||||
);
|
||||
typedef MessageTransformer =
|
||||
Map<String, dynamic> Function(Map<String, dynamic> raw);
|
||||
|
||||
/// WebSocket 连接管理
|
||||
///
|
||||
@@ -39,19 +37,26 @@ typedef MessageTransformer = Map<String, dynamic> Function(
|
||||
///
|
||||
/// ```
|
||||
/// 登录成功 → connect(token) → 前置检查 → 建立连接
|
||||
/// App 进后台 → onEnterBackground() → 断开连接(省电)
|
||||
/// App 回前台 → onEnterForeground() → 检查网络 → 自动重连
|
||||
///
|
||||
/// ── disconnectInBackground = true(默认,移动端)──
|
||||
/// App 进后台 → onEnterBackground() → 暂停心跳 + 断开连接(省电)
|
||||
/// App 回前台 → onEnterForeground() → 恢复心跳 → onBeforeReconnect → 重连
|
||||
///
|
||||
/// ── disconnectInBackground = false(桌面端)──
|
||||
/// App 进后台 → onEnterBackground() → 不操作,完全保活
|
||||
/// App 回前台 → onEnterForeground() → 不操作(连接始终在线)
|
||||
///
|
||||
/// 网络丢失 → handleNetworkLost() → 断开连接
|
||||
/// 网络恢复 → handleNetworkRestored() → 退避重连(防抖动)
|
||||
/// 网络恢复 → handleNetworkRestored() → 退避 → onBeforeReconnect → 重连
|
||||
/// 登出 → disconnect() → 断开连接,清除 token
|
||||
/// ```
|
||||
///
|
||||
/// ## 前置检查策略
|
||||
///
|
||||
/// 所有会发起网络操作的方法都先检查前置条件:
|
||||
/// - connect → 检查网络可用性 + 是否在后台
|
||||
/// - send / sendString → 检查连接状态 + 是否在后台
|
||||
/// - onEnterForeground 重连 → 检查网络可用性
|
||||
/// - connect → 检查网络可用性 + 是否在后台(仅 disconnectInBackground=true 时拦截)
|
||||
/// - send / sendString → 检查连接状态 + 是否在后台(仅 disconnectInBackground=true 时拦截)
|
||||
/// - onEnterForeground / 网络恢复重连 → 检查网络可用性 + onBeforeReconnect
|
||||
class SocketManager {
|
||||
final NetworksMessagingApi _client;
|
||||
final String _wsUrl;
|
||||
@@ -70,6 +75,22 @@ class SocketManager {
|
||||
/// 连接和重连前调用,无网络时跳过操作并标记恢复时重试。
|
||||
final Future<bool> Function()? onCheckNetworkAvailable;
|
||||
|
||||
/// 重连前回调
|
||||
///
|
||||
/// 在 WebSocket 重连前调用(前台恢复、网络恢复),App 层用于:
|
||||
/// - 检查并刷新即将过期的 token
|
||||
/// - 更新连接参数
|
||||
///
|
||||
/// 回调完成后才发起实际重连。
|
||||
final Future<void> Function()? onBeforeReconnect;
|
||||
|
||||
/// 进后台时是否断开连接
|
||||
///
|
||||
/// true(默认)— 后台断连省电,由 push 通知兜底,前台恢复时自动重连。
|
||||
/// false — 后台保持连接(适用于桌面端或需要后台实时推送的场景)。
|
||||
/// 设为 false 时,后台仅暂停心跳,不主动断连。
|
||||
final bool disconnectInBackground;
|
||||
|
||||
/// 日志回调
|
||||
final void Function(String message, {String? tag})? onLog;
|
||||
|
||||
@@ -104,10 +125,12 @@ class SocketManager {
|
||||
required NetworksMessagingApi client,
|
||||
required String wsUrl,
|
||||
this.onMessageTransform,
|
||||
this.onBeforeReconnect,
|
||||
this.disconnectInBackground = true,
|
||||
this.onCheckNetworkAvailable,
|
||||
this.onLog,
|
||||
}) : _client = client,
|
||||
_wsUrl = wsUrl;
|
||||
}) : _client = client,
|
||||
_wsUrl = wsUrl;
|
||||
|
||||
// ── 连接 ──────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -124,8 +147,8 @@ class SocketManager {
|
||||
_reconnectOnForeground = false;
|
||||
_reconnectOnNetworkRestore = false;
|
||||
|
||||
// 前置检查:在后台不连接(省电)
|
||||
if (_isInBackground) {
|
||||
// 前置检查:移动端模式下在后台不连接(省电)
|
||||
if (_isInBackground && disconnectInBackground) {
|
||||
_reconnectOnForeground = true;
|
||||
_log('In background, defer connect to foreground');
|
||||
return false;
|
||||
@@ -165,26 +188,47 @@ class SocketManager {
|
||||
/// 当前是否在后台
|
||||
bool get isInBackground => _isInBackground;
|
||||
|
||||
/// Token 热更新
|
||||
///
|
||||
/// 透传给 SocketClient,仅更新内部 token,不断开连接。
|
||||
/// 适用于 HTTP 层 token 刷新后同步到 WebSocket 的场景。
|
||||
void updateToken(String token) {
|
||||
_lastToken = token;
|
||||
_client.updateToken(token);
|
||||
_log('Token updated via SocketManager');
|
||||
}
|
||||
|
||||
// ── 前后台生命周期 ────────────────────────────────────────────────────────
|
||||
//
|
||||
// 后台 → 断连(省电省流量)
|
||||
// 后台 → 断连(省电省流量)或保持连接(桌面端)
|
||||
// 前台 → 自动重连(如果之前有连接)
|
||||
|
||||
/// App 进后台 → 断开连接,标记前台恢复时重连
|
||||
/// App 进后台
|
||||
///
|
||||
/// 由 App 层 WidgetsBindingObserver 在 [AppLifecycleState.paused] 时调用。
|
||||
/// 后台保持连接会消耗电量和流量,断开后由 push 通知兜底。
|
||||
///
|
||||
/// [disconnectInBackground] 为 true 时(默认,移动端):
|
||||
/// 断开连接 + 暂停心跳,由 push 通知兜底,前台恢复时自动重连。
|
||||
///
|
||||
/// [disconnectInBackground] 为 false 时(桌面端):
|
||||
/// 不断连、不暂停心跳,WebSocket 完全保活。
|
||||
void onEnterBackground() {
|
||||
_isInBackground = true;
|
||||
// 取消待执行的前台重连(防止快速 前台→后台 切换导致后台建连)
|
||||
_foregroundReconnectTimer?.cancel();
|
||||
_foregroundReconnectTimer = null;
|
||||
// 同步 SocketClient 内部状态(与 onEnterForeground 对称)
|
||||
|
||||
if (!disconnectInBackground) {
|
||||
// 桌面端模式:不断连、不暂停心跳,完全保活
|
||||
_log('Entering background, keeping connection alive');
|
||||
return;
|
||||
}
|
||||
|
||||
// 移动端模式:通知 SocketClient 进后台(暂停心跳)
|
||||
_client.onEnterBackground();
|
||||
|
||||
if (_lastToken == null) return; // 未登录,无需处理
|
||||
|
||||
// 与 _handleNetworkLost 保持一致:
|
||||
// 不仅 connected,connecting / reconnecting 也要断开,
|
||||
// 防止 SocketClient 在后台继续尝试连接浪费电量和流量。
|
||||
if (_client.isConnected ||
|
||||
@@ -202,7 +246,11 @@ class SocketManager {
|
||||
/// 重连前检查网络可用性,无网络时延迟到网络恢复事件再连。
|
||||
void onEnterForeground() {
|
||||
_isInBackground = false;
|
||||
_client.onEnterForeground();
|
||||
|
||||
// 只在移动端模式(后台曾断连/暂停心跳)时通知 SocketClient 恢复
|
||||
if (disconnectInBackground) {
|
||||
_client.onEnterForeground();
|
||||
}
|
||||
|
||||
if (_reconnectOnForeground && _lastToken != null) {
|
||||
_reconnectOnForeground = false;
|
||||
@@ -226,7 +274,12 @@ class SocketManager {
|
||||
_log('Network unavailable, defer reconnect to network restore');
|
||||
return;
|
||||
}
|
||||
_client.connect(_wsUrl, token: _lastToken!);
|
||||
// 重连前钩子:刷新即将过期的 token 等
|
||||
await onBeforeReconnect?.call();
|
||||
// token 可能被 onBeforeReconnect 更新(通过 updateToken 链路同步)
|
||||
if (_lastToken != null && !_client.isConnected) {
|
||||
_client.connect(_wsUrl, token: _lastToken!);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -275,18 +328,22 @@ class SocketManager {
|
||||
if (_reconnectOnNetworkRestore && _lastToken != null) {
|
||||
_reconnectOnNetworkRestore = false;
|
||||
|
||||
// 在后台不重连,等前台恢复时再连
|
||||
if (_isInBackground) {
|
||||
// 移动端模式:在后台不重连,等前台恢复时再连
|
||||
if (_isInBackground && disconnectInBackground) {
|
||||
_reconnectOnForeground = true;
|
||||
_log('Network restored but in background, defer to foreground');
|
||||
return;
|
||||
}
|
||||
|
||||
_log('Network restored, scheduling reconnect with backoff');
|
||||
_networkDebouncer.call(() {
|
||||
_networkDebouncer.call(() async {
|
||||
if (!_client.isConnected && _lastToken != null && !_isInBackground) {
|
||||
_log('Backoff timer fired, reconnecting');
|
||||
_client.connect(_wsUrl, token: _lastToken!);
|
||||
// 重连前钩子:刷新即将过期的 token 等
|
||||
await onBeforeReconnect?.call();
|
||||
if (!_client.isConnected && _lastToken != null && !_isInBackground) {
|
||||
_log('Backoff timer fired, reconnecting');
|
||||
_client.connect(_wsUrl, token: _lastToken!);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -308,6 +365,9 @@ class SocketManager {
|
||||
/// 原始消息流(不经预处理,调试用)
|
||||
Stream<String> get rawMessageStream => _client.rawMessageStream;
|
||||
|
||||
/// 二进制消息流
|
||||
Stream<dynamic> get binaryMessageStream => _client.binaryMessageStream;
|
||||
|
||||
/// 连接状态变化流
|
||||
Stream<SocketConnectionState> get connectionStateStream =>
|
||||
_client.connectionStateStream;
|
||||
@@ -333,6 +393,14 @@ class SocketManager {
|
||||
return _client.sendString(message);
|
||||
}
|
||||
|
||||
/// 发送二进制数据
|
||||
///
|
||||
/// 前置检查:未连接或在后台时不发送。
|
||||
Future<bool> sendBytes(List<int> bytes) {
|
||||
if (!_canSend()) return Future.value(false);
|
||||
return _client.sendBytes(bytes);
|
||||
}
|
||||
|
||||
// ── 释放 ──────────────────────────────────────────────────────────────────
|
||||
|
||||
/// 释放所有资源
|
||||
@@ -355,7 +423,7 @@ class SocketManager {
|
||||
_log('Not connected, cannot send');
|
||||
return false;
|
||||
}
|
||||
if (_isInBackground) {
|
||||
if (_isInBackground && disconnectInBackground) {
|
||||
_log('In background, skip send');
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user