Android WebSocket 连接管理:心跳保活与断线重连避坑
## Android WebSocket:心跳保活与断线重连问题
Android WebSocket:心跳保活与断线重连最容易误判成网络抖动,但很多现场其实是连接状态机写得太松:前台退后台后连接没及时续命,或者重连任务叠加,最后把一条长连接问题放大成消息丢失和重复登录。
## 方案
先把连接状态统一收口成单一状态机;再把心跳、超时、重连退避拆开;最后加一层前后台切换时的连接策略。这个顺序能先止住最常见的乱重连、假在线和消息延迟堆积。进一步落地时,建议把连接建立、心跳发送、断线回调、指数退避、手动关闭五个动作拆成独立函数,并分别打点记录。这样一旦线上出现假在线问题,团队可以很快从日志把责任点缩到心跳、网络切换或重连退避其中一层。
很多长连接问题真正难的不是修一次,而是修完之后下一次同类故障还能不能被快速定位。所以这层方案里最好顺手补上:连接状态枚举、最近一次心跳时间、最近一次重连原因、重连次数上限和人工断开标记。只要这些状态被稳定记录,后续排障成本会直接降一档。
## 示例代码
1. 连接管理器
class WsManager(private val client: OkHttpClient, private val request: Request) {
private var socket: WebSocket? = null
fun connect(listener: WebSocketListener) {
socket = client.newWebSocket(request, listener)
}
fun sendHeartbeat() {
socket?.send("ping")
}
fun close() {
socket?.close(1000, "manual")
}
}
2. 调试命令
adb logcat -d | grep -i WebSocket
adb shell dumpsys connectivity
3. 重连退避
fun nextBackoffSeconds(retry: Int): Long {
return minOf(60, 1L shl retry)
}
## 注意点
先盯三类:一是只判断 onOpen 不判断真实可发消息状态;二是网络切换后旧 socket 没销毁;三是重连策略没有退避,导致服务端被打爆。很多所谓网络不稳,本质都是客户端状态机没收紧。
## 报错与排查
1. 频繁断线
先分清楚是服务端踢掉、NAT 超时,还是客户端自己把连接挂死。看心跳日志和前后台切换时间点,通常第一轮就能缩小范围。
adb logcat -d | grep -i timeout
2. 消息重复
如果一条业务消息被消费两次,先查是不是旧连接没关干净,又开了新连接。不要只盯服务端幂等,先把客户端连接唯一性钉死。
data class WsMessage(val id: String, val body: String)
fun shouldConsume(cache: MutableSet, id: String): Boolean = cache.add(id)
## 可运行片段
1. 最小状态对象
data class WsState(
val connected: Boolean,
val retryCount: Int,
val lastHeartbeatAt: Long
)
2. 演示入口
fun printWsState() {
println(WsState(true, 0, System.currentTimeMillis()))
}
3. 验证命令
adb logcat -d | grep -i WebSocket
adb shell dumpsys connectivity
## 结论
Android WebSocket:心跳保活与断线重连这类问题,关键不是补更多重连代码,而是先把连接状态、心跳策略和重连退避拆清楚。只要状态机收口,长连接的大多数假故障都会自己消失。
