sharedworker 是浏览器端唯一能跨标签页、跨 iframe 统一管理网络保活的原生机制,因其不受页面后台冻结限制,可稳定执行心跳避免移动端无谓唤醒;需将 ping/pong 全部下沉至 worker 内完成,并配合页面生命周期消息协同清理资源,同时为 safari 提供降级兜底。

SharedWorker 是目前浏览器端唯一能跨标签页、跨 iframe 统一管理网络保活逻辑的原生机制,特别适合在移动端降低后台唤醒频率——它让心跳只在一个线程里跑,不随页面切后台而暂停,也不因多个窗口重复触发。
为什么 SharedWorker 能减少移动端唤醒
移动端浏览器(尤其是 iOS Safari 和 Android Chrome)对页面后台运行限制极严:页面不可见时,主线程定时器(setInterval、setTimeout)会大幅降频甚至冻结,导致心跳失效、连接被服务端误判下线、进而频繁重连并唤醒进程。SharedWorker 不受此限制,只要同源还有任一页面存活(哪怕只是后台标签),它就持续运行,心跳可稳定每 30 秒执行一次,避免因“假掉线”引发的无谓唤醒。
心跳必须完全下沉到 SharedWorker 内部
不能把 ping 逻辑放在页面里,也不能靠 Service Worker 转发——前者会被冻结,后者无法主动发起请求。所有保活动作必须由 SharedWorker 独立完成:
- 用 setInterval(() => ws.send("ping"), 30000) 发送心跳,不依赖页面活跃状态
- 监听 ws.onmessage 捕获服务端返回的 "pong",每次收到即更新本地活跃标记
- 连续两次未收到 pong(如超时 45 秒),主动 ws.close() 并触发重连流程
- 心跳消息体保持极简(例如 {"type":"ping","ts":1745787120}),避免序列化开销和传输延迟
配合页面生命周期做优雅协同
SharedWorker 自身不感知页面关闭,需页面主动配合,才能避免无效心跳和资源残留:
- 页面加载后立即发送 { type: "init", clientId: "tab-xxx" },Worker 为其分配 port 并记录最后活跃时间
- 页面进入后台(document.hidden === true)时不暂停心跳,但可暂停非关键业务轮询
- 页面卸载前(beforeunload 或 pagehide)发送 { type: "disconnect" },Worker 清理对应 port 和订阅关系
- Worker 内启动一个 30 秒检查循环,自动清理超过阈值未响应的 port,防止僵尸连接堆积
兼容 Safari 的降级兜底设计
iOS Safari 对 SharedWorker 支持有限(仅 16.4+ 完整支持),需提前检测并 fallback:
- 创建 SharedWorker 前先判断 typeof SharedWorker !== "undefined"
- 若不支持,退回到单页面内的心跳 + 可见性感知模式:仅在 document.visibilityState === "visible" 时启用心跳,后台时依赖服务端长连接维持
- 所有通信协议保持一致(如统一用 type/clientId 字段),确保降级后业务逻辑无需修改











