第三章 网络状态实时监测

离线优先应用的核心前提是时刻知晓当前网络的可达性与连接质量。仅依赖单一的检测手段往往不可靠,因此需要组合 Navigator.onLine 属性、浏览器原生 online/offline 事件以及应用层心跳检测机制,形成多级、低延迟的感知体系。

3.1 基本原理与权衡

检测手段原理实时性可靠性场景
navigator.onLine浏览器对网络连通的布尔判断即时(静态快照)低(代理缓存、假在线)初始状态检查
online/offline 事件浏览器通知网络状态变化高(事件驱动)中(部分 WebView 不触发)实时状态切换
心跳检测定时发送轻量请求验证真实可达性取决于间隔高(突破浏览器限制)后台保活、临界检测
核心原则: 以 online/offline 事件为主驱动,以 navigator.onLine 为初始快照,以心跳检测作为降级兜底,三者在应用运行时协作完成网络状态的实时更新。

3.2 实现流程

  1. 页面加载时读取 navigator.onLine 设置初始状态。
  2. 注册 window.addEventListener('online', handler) 和 offline 监听,立刻更新状态并触发业务回调。
  3. 启动心跳检测定时器(建议 15-30 秒一次),向服务器或已知端点发送 HEAD/GET 请求,根据响应码或超时判定在线与否。
  4. 当事件或心跳检测到状态变化时,更新全局状态机,并存储上次变更时间戳用于日志/分析。
  5. 心跳检测结果若与事件状态不一致,以心跳结果为准,并记录脱节次数用于调试。

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 体系架构与决策流

网络状态实时监测决策流 应用启动 读取 navigator.onLine 初始状态 注册 online/offline 事件 window.addEventListener 启动心跳检测定时器 fetch(HEAD) 每15~30s 更新全局网络状态 调用 onOnline/onOffline 切换离线/在线工作模式 ⏱ 心跳检测应在 应用进入后台时降低频率 或使用 Page Visibility API 暂停

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%(大于预期网络可用度)。