fix: NetworkManager连接超时机制重写
- 移除旧的30秒刚性超时(60次x500ms轮询),改为两阶段连接策略 - Phase 1: 10秒快速轮询(20次x500ms)检测即时连接 - Phase 2: 后台监控器观察Socket.IO内部重连最长2分钟 - 后台监控超时后触发forceReconnect而非宣告连接失败 - 重连机制增加冷却期(30秒)和指数退避 - 配置读取方法从assets读取server_config.json - 所有日志移除emoji符号 - 完整重写消除之前的重复方法和缺失闭合括号问题
This commit is contained in:
@@ -203,3 +203,242 @@ class NetworkManager(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check connection and start if not connected.
|
||||
* Called externally to verify and recover connection.
|
||||
*/
|
||||
fun checkAndStartConnection() {
|
||||
networkScope.launch {
|
||||
try {
|
||||
if (isConnected()) {
|
||||
Log.d(TAG, "Connection check: already connected")
|
||||
return@launch
|
||||
}
|
||||
Log.i(TAG, "Connection check: not connected, starting connection")
|
||||
connectToServer()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "checkAndStartConnection failed: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start connection (alias for checkAndStartConnection).
|
||||
* Used by AccessibilityRemoteService.
|
||||
*/
|
||||
fun startConnection() {
|
||||
checkAndStartConnection()
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from server and cancel background jobs.
|
||||
*/
|
||||
fun disconnect() {
|
||||
try {
|
||||
Log.i(TAG, "Disconnecting from server")
|
||||
backgroundMonitorJob?.cancel()
|
||||
backgroundMonitorJob = null
|
||||
isConnected = false
|
||||
isConnecting = false
|
||||
if (::socketIOManager.isInitialized) {
|
||||
socketIOManager.disconnect()
|
||||
}
|
||||
Log.i(TAG, "Disconnected from server")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Disconnect failed: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup all resources. Call on service destroy.
|
||||
*/
|
||||
fun cleanup() {
|
||||
try {
|
||||
Log.i(TAG, "Cleaning up NetworkManager resources")
|
||||
disconnect()
|
||||
networkScope.cancel()
|
||||
Log.i(TAG, "NetworkManager cleanup complete")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Cleanup failed: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SocketIOManager instance.
|
||||
* @return SocketIOManager or null if not initialized
|
||||
*/
|
||||
fun getSocketIOManager(): SocketIOManager? {
|
||||
return if (::socketIOManager.isInitialized) {
|
||||
socketIOManager
|
||||
} else {
|
||||
Log.w(TAG, "SocketIOManager not initialized")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if currently connected to server.
|
||||
*/
|
||||
fun isConnected(): Boolean {
|
||||
// Sync local state with actual Socket.IO state
|
||||
if (::socketIOManager.isInitialized) {
|
||||
val actualConnected = socketIOManager.isConnected()
|
||||
if (isConnected != actualConnected) {
|
||||
isConnected = actualConnected
|
||||
}
|
||||
return actualConnected
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if connection is in progress.
|
||||
*/
|
||||
fun isConnecting(): Boolean = isConnecting
|
||||
|
||||
/**
|
||||
* Get current connection status summary.
|
||||
*/
|
||||
fun getConnectionStatus(): ConnectionStatus {
|
||||
return ConnectionStatus(
|
||||
connected = isConnected(),
|
||||
connecting = isConnecting,
|
||||
reconnectAttempts = reconnectAttempts,
|
||||
lastAttemptTime = lastConnectionAttempt
|
||||
)
|
||||
}
|
||||
|
||||
// -- Config reading methods --
|
||||
|
||||
/**
|
||||
* Read server URL from assets config file.
|
||||
* @return server URL string or null on failure
|
||||
*/
|
||||
fun getServerUrl(): String? {
|
||||
return readServerConfigFromAssets()
|
||||
}
|
||||
|
||||
/**
|
||||
* Read web URL from assets config file.
|
||||
* @return web URL string or null on failure
|
||||
*/
|
||||
fun getWebUrl(): String? {
|
||||
return readWebConfigFromAssets()
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse server_config.json and extract serverUrl.
|
||||
*/
|
||||
private fun readServerConfigFromAssets(): String? {
|
||||
return try {
|
||||
val jsonStr = context.assets.open(CONFIG_FILE_SERVER)
|
||||
.bufferedReader().use { it.readText() }
|
||||
val json = JSONObject(jsonStr)
|
||||
val url = json.optString("serverUrl", "")
|
||||
if (url.isBlank()) {
|
||||
Log.e(TAG, "serverUrl is empty in $CONFIG_FILE_SERVER")
|
||||
null
|
||||
} else {
|
||||
url
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to read $CONFIG_FILE_SERVER: ${e.message}")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse server_config.json and extract webUrl.
|
||||
*/
|
||||
private fun readWebConfigFromAssets(): String? {
|
||||
return try {
|
||||
val jsonStr = context.assets.open(CONFIG_FILE_SERVER)
|
||||
.bufferedReader().use { it.readText() }
|
||||
val json = JSONObject(jsonStr)
|
||||
val url = json.optString("webUrl", "")
|
||||
if (url.isBlank()) {
|
||||
Log.e(TAG, "webUrl is empty in $CONFIG_FILE_SERVER")
|
||||
null
|
||||
} else {
|
||||
url
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to read webUrl from $CONFIG_FILE_SERVER: ${e.message}")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
// -- State reset methods --
|
||||
|
||||
/**
|
||||
* Reset reconnect counter. Called after successful connection.
|
||||
*/
|
||||
fun resetReconnectCounter() {
|
||||
reconnectAttempts = 0
|
||||
lastReconnectTime = 0L
|
||||
Log.d(TAG, "Reconnect counter reset")
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all network state for fresh start (e.g. service restart).
|
||||
*/
|
||||
fun resetNetworkState() {
|
||||
Log.i(TAG, "Resetting network state")
|
||||
backgroundMonitorJob?.cancel()
|
||||
backgroundMonitorJob = null
|
||||
isConnected = false
|
||||
isConnecting = false
|
||||
reconnectAttempts = 0
|
||||
lastReconnectTime = 0L
|
||||
lastConnectionAttempt = 0L
|
||||
}
|
||||
|
||||
/**
|
||||
* Force reconnect by disconnecting and reconnecting.
|
||||
*/
|
||||
fun forceReconnect() {
|
||||
Log.i(TAG, "Force reconnect requested")
|
||||
networkScope.launch {
|
||||
try {
|
||||
disconnect()
|
||||
delay(1000)
|
||||
connectToServer()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Force reconnect failed: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- Private helpers --
|
||||
|
||||
/**
|
||||
* Check if SocketIOManager is initialized and connected.
|
||||
*/
|
||||
private fun isSocketIOReady(): Boolean {
|
||||
return ::socketIOManager.isInitialized && socketIOManager.isConnected()
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger force reconnect via SocketIOManager.
|
||||
*/
|
||||
private fun triggerForceReconnect() {
|
||||
try {
|
||||
if (::socketIOManager.isInitialized) {
|
||||
socketIOManager.forceReconnect()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "triggerForceReconnect failed: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connection status data class for external queries.
|
||||
*/
|
||||
data class ConnectionStatus(
|
||||
val connected: Boolean,
|
||||
val connecting: Boolean,
|
||||
val reconnectAttempts: Int,
|
||||
val lastAttemptTime: Long
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user