移动端熄屏后websocket被系统强制冻结,前端需通过visibilitychange监听状态、指数退避重连、离线消息缓存与幂等、pwa service worker兜底及服务端协同来保障消息可靠。

移动端熄屏后WebSocket被杀掉,不是你的代码问题,而是iOS/Android系统强制冻结JS执行、停掉网络连接导致的。你没法“保持长连接”,只能做到“快速感知断连 + 可靠重建 + 状态不丢”。
监听 visibilitychange 是第一道防线
页面不可见(熄屏、切后台、切Tab)时,document.visibilityState 会变成 hidden;恢复时变回 visible。这是前端唯一能稳定捕获的系统级信号。
- 不要在
hidden时调用ws.close()—— 连接大概率已被系统掐断,再 close 会抛InvalidStateError - 记录
hidden时间戳,切回visible后对比间隔:若超过服务端心跳超时(比如 45 秒),直接走重连流程,别赌旧连接还活着 - Vue/React 项目中,确保事件监听绑定在
mounted/useEffect,且在卸载时removeEventListener,避免内存泄漏
重连必须带指数退避和状态校验
盲目 setInterval(() => new WebSocket(...), 1000) 会触发重连风暴,服务器扛不住,用户流量也浪费。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 重连间隔从 1s 开始,每次失败翻倍(1s → 2s → 4s → 8s),上限设为 30s,避免卡死在弱网环境
- 每次重连前检查
ws.readyState:只有WebSocket.CONNECTING或WebSocket.CLOSED才新建;OPEN说明可能还没断,先发个业务心跳确认 - 重连成功后,立即拉一次未读摘要(如
/api/messages/unread),而不是等第一条onmessage,防止消息空窗
离线消息缓存和幂等是防重复的关键
用户在熄屏期间发的消息,不能丢,也不能因重连而重复提交。
- 发送消息前,先存入
localStorage或IndexedDB,标记status: "pending";收到服务端 ACK 后才删掉 - 每条消息带上唯一
clientMessageId(可用Date.now() + Math.random()拼),服务端做幂等判重 - 切回前台重连成功后,遍历 pending 队列补发 —— 注意顺序,别把后发的先推上去
PWA 场景下可加一层 Service Worker 缓存兜底
普通 H5 页面做不到后台保活,但 PWA 的 Service Worker 能在连接断开时暂存请求,并在网络恢复后自动重试。
- 只缓存关键接口:WebSocket 握手前的鉴权响应(如
/api/auth/token)、离线消息上报接口 - 在
fetch事件里拦截 POST 请求,若网络不可用,写入cache.put()并标记pending: true - 监听
sync事件,在网络恢复后批量重发,但注意:SW 本身也会被系统终止,不适合存大消息或长周期任务
真正难的不是写重连逻辑,而是让服务端配合做连接状态兜底——前端永远不知道连接是不是“假在线”,必须靠服务端心跳确认+主动推送离线摘要,否则再精细的前端策略也挡不住消息丢失。










