第三章 网络状态实时监测
离线优先应用的核心前提是时刻知晓当前网络的可达性与连接质量。仅依赖单一的检测手段往往不可靠,因此需要组合 Navigator.onLine 属性、浏览器原生 online/offline 事件以及应用层心跳检测机制,形成多级、低延迟的感知体系。
3.1 基本原理与权衡
| 检测手段 | 原理 | 实时性 | 可靠性 | 场景 |
|---|---|---|---|---|
navigator.onLine | 浏览器对网络连通的布尔判断 | 即时(静态快照) | 低(代理缓存、假在线) | 初始状态检查 |
online/offline 事件 | 浏览器通知网络状态变化 | 高(事件驱动) | 中(部分 WebView 不触发) | 实时状态切换 |
| 心跳检测 | 定时发送轻量请求验证真实可达性 | 取决于间隔 | 高(突破浏览器限制) | 后台保活、临界检测 |
核心原则: 以
online/offline 事件为主驱动,以 navigator.onLine 为初始快照,以心跳检测作为降级兜底,三者在应用运行时协作完成网络状态的实时更新。
3.2 实现流程
- 页面加载时读取
navigator.onLine设置初始状态。 - 注册
window.addEventListener('online', handler)和offline监听,立刻更新状态并触发业务回调。 - 启动心跳检测定时器(建议 15-30 秒一次),向服务器或已知端点发送 HEAD/GET 请求,根据响应码或超时判定在线与否。
- 当事件或心跳检测到状态变化时,更新全局状态机,并存储上次变更时间戳用于日志/分析。
- 心跳检测结果若与事件状态不一致,以心跳结果为准,并记录脱节次数用于调试。
3.3 核心 API 与代码实现
class NetworkMonitor {
constructor({
heartbeatUrl = 'https://example.com/ping',
heartbeatInterval = 15000,
timeout = 3000,
onOnline = () => {},
onOffline = () => {},
onStatusChange = () => {}
} = {}) {
this.heartbeatUrl = heartbeatUrl;
this.heartbeatInterval = heartbeatInterval;
this.timeout = timeout;
this.onOnline = onOnline;
this.onOffline = onOffline;
this.onStatusChange = onStatusChange;
this._timer = null;
this._status = navigator.onLine;
this._bindEvents();
this._startHeartbeat();
}
/** 当前网络状态 */
get status() { return this._status; }
/** 绑定浏览器原生事件 */
_bindEvents() {
window.addEventListener('online', () => {
this._updateStatus(true, 'event:online');
});
window.addEventListener('offline', () => {
this._updateStatus(false, 'event:offline');
});
}
/** 心跳检测 - 使用 fetch 与 AbortController 实现超时 */
async _heartbeat() {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), this.timeout);
try {
const res = await fetch(this.heartbeatUrl, {
method: 'HEAD',
mode: 'no-cors', // 跨域场景下仍能检测可达性
signal: controller.signal
});
this._updateStatus(true, 'heartbeat:reachable');
} catch (err) {
this._updateStatus(false, 'heartbeat:unreachable');
} finally {
clearTimeout(id);
}
}
_updateStatus(newStatus, source) {
if (newStatus === this._status) return;
console.log(`[NetworkMonitor] status change: ${newStatus} (${source})`);
this._status = newStatus;
this.onStatusChange(newStatus);
newStatus ? this.onOnline() : this.onOffline();
}
_startHeartbeat() {
this._timer = setInterval(() => this._heartbeat(), this.heartbeatInterval);
}
destroy() {
clearInterval(this._timer);
window.removeEventListener('online', this._updateStatus);
window.removeEventListener('offline', this._updateStatus);
}
}
// 使用示例
const monitor = new NetworkMonitor({
heartbeatUrl: '/api/ping',
heartbeatInterval: 20000,
onOnline: () => { console.log('Ready to sync'); },
onOffline: () => { console.log('Enter offline mode'); }
});
3.4 体系架构与决策流
3.5 调试与常见错误
调试技巧:
- 浏览器 DevTools → Network → 切换“Offline”模拟
offline事件。 - 使用
about://net-internals/#events查看 Chrome 网络事件日志。 - 在控制台手动执行
window.dispatchEvent(new Event('offline'))触发监听。
常见错误:
navigator.onLine在 iOS Safari 中可能误报(例如通过 Wi-Fi 但无外网)。online/offline事件在 Android WebView 或自定义 UA 中可能不被触发,需要依赖心跳检测补位。- 心跳检测 URL 必须支持
no-cors模式,否则跨域场景将收到 0 状态码导致误判离线。 - 心跳间隔太短(< 5s)造成设备频繁唤醒,续航下降;太长(> 60s)导致从离线上线恢复延迟。
- 未处理
AbortController超时,请求挂起导致累积并发,阻塞主线程。
3.6 性能与稳定性优化
| 优化项 | 策略 | 效果 |
|---|---|---|
| 心跳间隔退避 | 连续在线时自动延长间隔(如 15s → 30s → 60s);检测到变化时重置为短间隔 | 减少无效请求,节省带宽 |
| 后台抑制 | 通过 document.visibilityState 在不可见时暂停心跳 | 降低 CPU 占用和电池消耗 |
| 冗余检测端点 | 配置多个心跳 URL(主/备用),主端失败后自动切换 | 避免单点故障导致的误判 |
| 结果缓存与去抖 | 心跳结果变化后在 500ms 内多次变化只触发一次业务回调 | 防止短暂网络抖动引发频繁切换 |
| 日志采样 | 仅记录状态变化事件,而非每次心跳结果 | 减小存储压力,快速定位问题 |
稳定性度量指标:
状态切换延迟 < 2s(从真实网络变化到业务感知);
误报告率 < 0.5%(心跳与真实状态不一致次数 / 总心跳次数);
心跳请求成功率 > 99%(大于预期网络可用度)。
状态切换延迟 < 2s(从真实网络变化到业务感知);
误报告率 < 0.5%(心跳与真实状态不一致次数 / 总心跳次数);
心跳请求成功率 > 99%(大于预期网络可用度)。