Merge remote-tracking branch 'origin/dev' into cody/netwrok_SDK
# Conflicts: # apps/im_app/lib/features/chat/presentation/chat_db_test_view_model.dart # apps/im_app/lib/features/login/presentation/login_view_model.dart 修复逻辑漏洞,性能优化
This commit is contained in:
@@ -16,7 +16,7 @@ typedef MessageTransformer =
|
||||
///
|
||||
/// 在 SocketClient(SDK 底层能力)之上封装:
|
||||
/// - 连接/断连生命周期(登录连接、登出断连)
|
||||
/// - 前后台生命周期(后台断连省电、前台自动重连)
|
||||
/// - 前后台生命周期(两种模式:后台断连 / 后台保活)
|
||||
/// - 网络状态响应(断网断连、恢复网络立即重连)
|
||||
/// - 操作前置检查(网络可用性 + 后台状态)
|
||||
/// - 消息预处理管道(通过 [onMessageTransform] 回调注入解密等)
|
||||
@@ -38,13 +38,13 @@ typedef MessageTransformer =
|
||||
/// ```
|
||||
/// 登录成功 → connect(token) → 前置检查 → 建立连接
|
||||
///
|
||||
/// ── disconnectInBackground = true(默认,移动端)──
|
||||
/// ── disconnectInBackground = true(后台断连模式)──
|
||||
/// App 进后台 → onEnterBackground() → 暂停心跳 + 断开连接(省电)
|
||||
/// App 回前台 → onEnterForeground() → 恢复心跳 → onBeforeReconnect → 重连
|
||||
///
|
||||
/// ── disconnectInBackground = false(桌面端)──
|
||||
/// App 进后台 → onEnterBackground() → 不操作,完全保活
|
||||
/// App 回前台 → onEnterForeground() → 不操作(连接始终在线)
|
||||
/// ── disconnectInBackground = false(后台保活模式,本项目默认)──
|
||||
/// App 进后台 → onEnterBackground() → 不操作,心跳不停、连接不断
|
||||
/// App 回前台 → onEnterForeground() → 检查连接健康,异常则重连
|
||||
///
|
||||
/// 网络丢失 → handleNetworkLost() → 断开连接
|
||||
/// 网络恢复 → handleNetworkRestored() → 退避 → onBeforeReconnect → 重连
|
||||
@@ -86,9 +86,9 @@ class SocketManager {
|
||||
|
||||
/// 进后台时是否断开连接
|
||||
///
|
||||
/// true(默认)— 后台断连省电,由 push 通知兜底,前台恢复时自动重连。
|
||||
/// false — 后台保持连接(适用于桌面端或需要后台实时推送的场景)。
|
||||
/// 设为 false 时,后台仅暂停心跳,不主动断连。
|
||||
/// true(SDK 默认)— 后台断连省电,由 push 通知兜底,前台恢复时自动重连。
|
||||
/// false(本项目使用)— 后台保持连接,心跳不停、请求不停,最大程度保活。
|
||||
/// 回前台时检查连接健康,异常则触发重连。
|
||||
final bool disconnectInBackground;
|
||||
|
||||
/// 日志回调
|
||||
@@ -147,7 +147,7 @@ class SocketManager {
|
||||
_reconnectOnForeground = false;
|
||||
_reconnectOnNetworkRestore = false;
|
||||
|
||||
// 前置检查:移动端模式下在后台不连接(省电)
|
||||
// 前置检查:后台断连模式下在后台不连接(省电)
|
||||
if (_isInBackground && disconnectInBackground) {
|
||||
_reconnectOnForeground = true;
|
||||
_log('In background, defer connect to foreground');
|
||||
@@ -200,18 +200,18 @@ class SocketManager {
|
||||
|
||||
// ── 前后台生命周期 ────────────────────────────────────────────────────────
|
||||
//
|
||||
// 后台 → 断连(省电省流量)或保持连接(桌面端)
|
||||
// 前台 → 自动重连(如果之前有连接)
|
||||
// 后台 → 保活(心跳不停、连接不断)或断连(省电模式)
|
||||
// 前台 → 检查连接健康 / 自动重连
|
||||
|
||||
/// App 进后台
|
||||
///
|
||||
/// 由 App 层 WidgetsBindingObserver 在 [AppLifecycleState.paused] 时调用。
|
||||
///
|
||||
/// [disconnectInBackground] 为 true 时(默认,移动端):
|
||||
/// 断开连接 + 暂停心跳,由 push 通知兜底,前台恢复时自动重连。
|
||||
///
|
||||
/// [disconnectInBackground] 为 false 时(桌面端):
|
||||
/// [disconnectInBackground] 为 false 时(后台保活,本项目默认):
|
||||
/// 不断连、不暂停心跳,WebSocket 完全保活。
|
||||
///
|
||||
/// [disconnectInBackground] 为 true 时(后台断连模式):
|
||||
/// 断开连接 + 暂停心跳,由 push 通知兜底,前台恢复时自动重连。
|
||||
void onEnterBackground() {
|
||||
_isInBackground = true;
|
||||
// 取消待执行的前台重连(防止快速 前台→后台 切换导致后台建连)
|
||||
@@ -219,12 +219,12 @@ class SocketManager {
|
||||
_foregroundReconnectTimer = null;
|
||||
|
||||
if (!disconnectInBackground) {
|
||||
// 桌面端模式:不断连、不暂停心跳,完全保活
|
||||
// 后台保活模式:不断连、不暂停心跳,不通知 SocketClient
|
||||
_log('Entering background, keeping connection alive');
|
||||
return;
|
||||
}
|
||||
|
||||
// 移动端模式:通知 SocketClient 进后台(暂停心跳)
|
||||
// 后台断连模式:通知 SocketClient 进后台(暂停心跳)
|
||||
_client.onEnterBackground();
|
||||
|
||||
if (_lastToken == null) return; // 未登录,无需处理
|
||||
@@ -240,50 +240,76 @@ class SocketManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// App 回前台 → 自动重连(如果之前后台断连)
|
||||
/// App 回前台
|
||||
///
|
||||
/// 由 App 层 WidgetsBindingObserver 在 [AppLifecycleState.resumed] 时调用。
|
||||
/// 重连前检查网络可用性,无网络时延迟到网络恢复事件再连。
|
||||
///
|
||||
/// 后台保活模式(disconnectInBackground=false):
|
||||
/// 检查连接健康,如果后台期间连接意外断开则自动重连。
|
||||
///
|
||||
/// 后台断连模式(disconnectInBackground=true):
|
||||
/// 通知 SocketClient 恢复心跳,然后重新建立连接。
|
||||
void onEnterForeground() {
|
||||
_isInBackground = false;
|
||||
|
||||
// 只在移动端模式(后台曾断连/暂停心跳)时通知 SocketClient 恢复
|
||||
if (disconnectInBackground) {
|
||||
// 后台断连模式:通知 SocketClient 恢复心跳
|
||||
_client.onEnterForeground();
|
||||
}
|
||||
|
||||
if (!disconnectInBackground && _lastToken != null) {
|
||||
// 后台保活模式:检查连接健康
|
||||
// 虽然后台期间心跳不停,但连接仍可能因网络切换、服务端关闭等原因断开。
|
||||
// SocketClient 的自动重连在后台也会工作(_isBackground=false),
|
||||
// 但回前台时兜底检查一次,确保连接可用。
|
||||
if (!_client.isConnected) {
|
||||
_log('Returning to foreground, connection lost, reconnecting...');
|
||||
_scheduleReconnect();
|
||||
} else {
|
||||
_log('Returning to foreground, connection healthy');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (_reconnectOnForeground && _lastToken != null) {
|
||||
// 后台断连模式:之前后台断连过,需要重连
|
||||
_reconnectOnForeground = false;
|
||||
_log('Returning to foreground, reconnecting...');
|
||||
// 延迟 500ms 等待网络稳定,通过 Timer 跟踪以便进后台时取消
|
||||
_foregroundReconnectTimer?.cancel();
|
||||
_foregroundReconnectTimer = Timer(
|
||||
const Duration(milliseconds: 500),
|
||||
() async {
|
||||
_foregroundReconnectTimer = null;
|
||||
// 双重保险:回调执行时再次检查后台状态
|
||||
if (_isInBackground) {
|
||||
_reconnectOnForeground = true;
|
||||
_log('Went back to background during delay, skip reconnect');
|
||||
_scheduleReconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/// 延迟 500ms 后执行重连
|
||||
///
|
||||
/// 等待网络稳定,通过 Timer 跟踪以便进后台时取消。
|
||||
void _scheduleReconnect() {
|
||||
_foregroundReconnectTimer?.cancel();
|
||||
_foregroundReconnectTimer = Timer(
|
||||
const Duration(milliseconds: 500),
|
||||
() async {
|
||||
_foregroundReconnectTimer = null;
|
||||
// 双重保险:回调执行时再次检查后台状态
|
||||
if (_isInBackground) {
|
||||
_reconnectOnForeground = true;
|
||||
_log('Went back to background during delay, skip reconnect');
|
||||
return;
|
||||
}
|
||||
if (!_client.isConnected && _lastToken != null) {
|
||||
// 前置检查:网络可用性
|
||||
if (!await _isNetworkAvailable()) {
|
||||
_reconnectOnNetworkRestore = true;
|
||||
_log('Network unavailable, defer reconnect to network restore');
|
||||
return;
|
||||
}
|
||||
if (!_client.isConnected && _lastToken != null) {
|
||||
// 前置检查:网络可用性
|
||||
if (!await _isNetworkAvailable()) {
|
||||
_reconnectOnNetworkRestore = true;
|
||||
_log('Network unavailable, defer reconnect to network restore');
|
||||
return;
|
||||
}
|
||||
// 重连前钩子:刷新即将过期的 token 等
|
||||
await onBeforeReconnect?.call();
|
||||
// token 可能被 onBeforeReconnect 更新(通过 updateToken 链路同步)
|
||||
if (_lastToken != null && !_client.isConnected) {
|
||||
_client.connect(_wsUrl, token: _lastToken!);
|
||||
}
|
||||
// 重连前钩子:刷新即将过期的 token 等
|
||||
await onBeforeReconnect?.call();
|
||||
// token 可能被 onBeforeReconnect 更新(通过 updateToken 链路同步)
|
||||
if (_lastToken != null && !_client.isConnected) {
|
||||
_client.connect(_wsUrl, token: _lastToken!);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// ── 网络状态变化 ──────────────────────────────────────────────────────────
|
||||
@@ -328,7 +354,7 @@ class SocketManager {
|
||||
if (_reconnectOnNetworkRestore && _lastToken != null) {
|
||||
_reconnectOnNetworkRestore = false;
|
||||
|
||||
// 移动端模式:在后台不重连,等前台恢复时再连
|
||||
// 后台断连模式:在后台不重连,等前台恢复时再连
|
||||
if (_isInBackground && disconnectInBackground) {
|
||||
_reconnectOnForeground = true;
|
||||
_log('Network restored but in background, defer to foreground');
|
||||
@@ -415,7 +441,10 @@ class SocketManager {
|
||||
|
||||
/// 发送前置检查
|
||||
///
|
||||
/// 两重保险:连接状态 + 后台状态。
|
||||
/// 后台保活模式(disconnectInBackground=false):只检查连接状态,
|
||||
/// 后台也能正常发送。
|
||||
///
|
||||
/// 后台断连模式(disconnectInBackground=true):额外检查后台状态,
|
||||
/// 后台已断连所以 isConnected 通常就能拦住,
|
||||
/// 但显式检查 _isInBackground 防止边界情况遗漏。
|
||||
bool _canSend() {
|
||||
@@ -424,7 +453,7 @@ class SocketManager {
|
||||
return false;
|
||||
}
|
||||
if (_isInBackground && disconnectInBackground) {
|
||||
_log('In background, skip send');
|
||||
_log('In background (disconnect mode), skip send');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user