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