移动端h5使用sse时需手动接管连接生命周期:监听页面可见性(visibilitychange)和网络状态(online/offline),隐藏时关闭eventsource,恢复可见且联网后延迟300–800ms重建;采用指数退避重连(初始1s,翻倍至最大30s),并限制最大重试次数或总时长。

移动端 H5 使用 SSE(Server-Sent Events)时,屏幕熄灭或页面进入后台会导致浏览器暂停 JavaScript 执行、网络连接中断(尤其是 iOS Safari 会主动关闭 EventSource 连接),从而断开 SSE 流。重连不能依赖默认的 EventSource 自动重试机制(它只在连接意外关闭时重试,且重试间隔不可控,后台状态下根本不会触发),必须手动接管连接生命周期并结合可见性与网络状态做精细化控制。
监听页面可见性 + 网络状态,主动管理连接
当页面被隐藏(如锁屏、切到其他 App)时,visibilitychange 事件会被触发;恢复可见时需判断是否需要重建连接。同时配合 online/offline 事件感知网络变化:
- 页面隐藏(
document.hidden === true)时,立即eventSource.close()并清空引用,避免残留连接或内存泄漏 - 页面重新可见(
document.hidden === false)且navigator.onLine === true时,延迟 300–800ms 后尝试重建连接(避开系统唤醒抖动) - 监听
online事件,在网络恢复后若页面可见,也触发重连(比如用户从地铁无网区出来)
实现带退避策略的手动重连循环
不要依赖 EventSource.reconnectDelay(后台不生效,iOS 常被忽略)。应封装自己的重连逻辑,使用指数退避防止服务端冲击:
- 初始重试间隔设为 1000ms,每次失败翻倍(最大不超过 30s)
- 每次重连前检查
document.hidden和navigator.onLine,任一为 false 则暂停重试,等待下次可见/联网事件 - 设置最大重试次数(如 10 次)或超时总时长(如 5 分钟),避免无限挂起
- 示例关键逻辑:
let retryCount = 0;
const maxRetries = 10;
let reconnectTimer = null;
function connect() {
if (document.hidden || !navigator.onLine) return;
eventSource = new EventSource('/api/sse');
eventSource.onopen = () => { retryCount = 0; };
eventSource.onerror = () => {
if (retryCount clearTimeout(reconnectTimer);
const delay = Math.min(1000 * 2 ** retryCount, 30000);
reconnectTimer = setTimeout(connect, delay);
retryCount++;
}
};
}
iOS Safari 特别处理:避免“假连接”和心跳失效
iOS Safari 在后台常表现为“连接未报错但收不到消息”,本质是内核冻结了 JS 和网络栈。仅靠 onerror 不可靠:
- 服务端必须开启心跳(如每 15–20s 发送
data: \n\n或自定义ping事件),前端监听onmessage或addEventListener('ping', ...) - 前端维护一个心跳计时器(
setTimeout),每次收到消息重置;若超时(如 30s 无任何事件),视为连接异常,主动 close + 触发重连 - 避免使用
fetch或XMLHttpRequest检测连通性(后台同样被冻结),心跳检测必须基于 SSE 流本身
补充健壮性措施
提升用户体验与调试能力:
- 连接成功后记录时间戳,页面恢复可见时对比上次成功接收时间,若间隔 > 60s,强制重连(防后台假连接)
- 在
onerror中打印错误类型(eventSource.readyState是 0 表示未连接,2 表示已关闭,可辅助判断) - 上线前务必在真机(尤其 iPhone)上测试锁屏 → 解锁 → 切换 App → 返回等全流程,模拟器无法复现真实后台行为
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











