diff --git a/AGENTS.md b/AGENTS.md index adc174e..c802706 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -211,6 +211,7 @@ gradlew installDebug - [x] 连接状态准确性修复:`ProtocolClient` 收到首个有效数据包后才标记 `CONNECTED`,不再 socket 创建后立即标记;`CONNECTING` 状态 5 秒无响应自动超时回退 `TIMEOUT` - [x] 连接按钮交互优化:`CONNECTING` 状态下点击连接按钮仅断开回到 `DISCONNECTED`,不再自动重连,由用户手动再次点击发起重试 - [x] 状态机补充:`RobotConnection` 允许 `CONNECTING → TIMEOUT` 合法转换 +- [x] 心跳启动时机修复:`ProtocolClient.connect()` 立即启动心跳线程,不再等待首次收到数据包后才启动;心跳在 `CONNECTING` 和 `CONNECTED` 状态下均正常运行 ### 待办 diff --git a/app/src/main/java/com/example/m20_gamepad/network/ProtocolClient.kt b/app/src/main/java/com/example/m20_gamepad/network/ProtocolClient.kt index a116ec2..ccab4cd 100644 --- a/app/src/main/java/com/example/m20_gamepad/network/ProtocolClient.kt +++ b/app/src/main/java/com/example/m20_gamepad/network/ProtocolClient.kt @@ -92,7 +92,8 @@ class ProtocolClient( * 2. 启动接收/发送协程 * 3. 发送订阅请求 * 4. 启动断线检测(CONNECTING 5s / CONNECTED 3s 无数据 → TIMEOUT) - * 5. 首次收到有效数据包后标记已连接,并启动心跳 1Hz(在接收循环中处理) + * 5. 启动心跳 1Hz(立即发送,覆盖 CONNECTING 和 CONNECTED 状态) + * 6. 首次收到有效数据包后标记已连接 */ fun connect() { if (_state.value != State.DISCONNECTED && _state.value != State.ERROR) return @@ -136,6 +137,11 @@ class ProtocolClient( timeoutJob = scope.launch { timeoutCheckLoop() } + + // 启动心跳(立即开始,覆盖 CONNECTING 和 CONNECTED 状态) + heartbeatJob = scope.launch(Dispatchers.IO) { + heartbeatLoop(sock, address) + } } catch (e: Exception) { _state.value = State.ERROR closeSocket() @@ -227,12 +233,9 @@ class ProtocolClient( _statusReport.tryEmit(report) } - // 首次收到有效数据包:标记已连接,启动心跳 + // 首次收到有效数据包:标记已连接 if (_state.value == State.CONNECTING) { _state.value = State.CONNECTED - heartbeatJob = scope.launch(Dispatchers.IO) { - heartbeatLoop(sock, remoteAddress!!) - } } } catch (_: SocketTimeoutException) { // 超时正常,继续循环 @@ -246,9 +249,11 @@ class ProtocolClient( } } - /** 心跳循环(1 Hz) */ + /** 心跳循环(1 Hz)—— 在 CONNECTING 和 CONNECTED 状态下运行 */ private suspend fun CoroutineScope.heartbeatLoop(sock: DatagramSocket, address: InetAddress) { - while (isActive && _state.value == State.CONNECTED) { + while (isActive && + (_state.value == State.CONNECTING || _state.value == State.CONNECTED) + ) { try { val json = ControlCommands.heartbeat() val packet = encoder.encode(json)