跳转至正文

如何在 Capacitor 应用中检测网络状态

在 Capacitor 应用中检测网络状态:读取在线/离线状态、监听变化、验证真实互联网连接,并处理计量网络。

在 Capacitor 移动应用中检测在线和离线网络状态

要在 Capacitor 应用中检测网络状态,请安装 @capacitor/network,调用 Network.getStatus() 获取当前状态,并订阅 networkStatusChange 以获取更新。这能告诉你设备是否已连接,以及用的是 Wi-Fi 还是蜂窝网络。若要确认互联网是否真的可用,或连接是否按流量计费,请添加可达性检查或 @capgo/capacitor-network-diagnostics。本指南用 Capacitor 8 code 覆盖了这三个层面。

“用户是否在线?”背后的三个问题

问题 API 备注
是否存在活跃的网络接口? Network.getStatus().connected 快速、事件驱动,但无法说明能否访问互联网
连接类型是什么? connectionType wifi, cellular, none, unknown 在官方插件中
互联网是否真的可用? HTTP 检查或 internetReachable 需要请求或操作系统验证
我应该避免大文件下载吗? expensive, constrained 按流量计费的网络与 iOS 低数据模式

大多数离线 bug 都源于把第一行当成第三行。带登录页的酒店 Wi-Fi 会报告 connected: true 而每次 API 调用都会超时。

安装 Network 插件

bun add @capacitor/network
bunx cap sync

无需配置。在 Android 上,该插件会向合并后的 manifest 添加 ACCESS_NETWORK_STATE ,这是一种在应用安装时自动授予的常规权限。iOS 则无需任何操作。

读取当前状态

import { Network } from '@capacitor/network';

const status = await Network.getStatus();
console.log(status.connected);      // true or false
console.log(status.connectionType); // 'wifi' | 'cellular' | 'none' | 'unknown'

在应用启动时调用它,以决定是从缓存还是网络加载。由于它读取的是操作系统状态而非发送流量,因此只需几毫秒即可返回。

监听网络变化

import { Network, type ConnectionStatus } from '@capacitor/network';
import type { PluginListenerHandle } from '@capacitor/core';

let handle: PluginListenerHandle | undefined;

export async function watchNetwork(onChange: (s: ConnectionStatus) => void) {
  onChange(await Network.getStatus());
  handle = await Network.addListener('networkStatusChange', onChange);
}

export async function stopWatchingNetwork() {
  await handle?.remove();
}

两个实用细节:

  • 对事件进行防抖处理。 从 Wi-Fi 切换到蜂窝网络时,两个平台都可能短暂报告 none 在新连接建立之前。在显示“离线”横幅前添加 1 到 2 秒的防抖,可以避免闪烁。
  • 移除监听器 在组件卸载时,否则在热重载和导航后回调会执行多次。

React Hook

import { useEffect, useState } from 'react';
import { Network, type ConnectionStatus } from '@capacitor/network';

export function useNetworkStatus() {
  const [status, setStatus] = useState<ConnectionStatus>({ connected: true, connectionType: 'unknown' });

  useEffect(() => {
    let timer: ReturnType<typeof setTimeout>;
    const listener = Network.addListener('networkStatusChange', (s) => {
      clearTimeout(timer);
      timer = setTimeout(() => setStatus(s), s.connected ? 0 : 1500);
    });
    Network.getStatus().then(setStatus);
    return () => {
      clearTimeout(timer);
      listener.then((l) => l.remove());
    };
  }, []);

  return status;
}

上线状态立即生效,下线状态等待 1.5 秒。同样的模式可以作为 Vue composable 或 Angular service 使用,带有 BehaviorSubject.

为什么“已连接”不代表在线

connected: true 来自操作系统网络栈。在以下情况下它保持为 true:

  • Wi-Fi 网络有强制门户(机场、酒店、火车)
  • 路由器没有上游连接
  • DNS 损坏或被过滤
  • 企业防火墙或 VPN 阻止了你的 API 域名
  • 你自己的后端挂了

解决办法是对你自己可控的某个服务做可达性检查:

export async function canReachApi(timeoutMs = 4000): Promise<boolean> {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeoutMs);
  try {
    const res = await fetch('https://api.example.com/health', {
      method: 'HEAD',
      cache: 'no-store',
      signal: controller.signal,
    });
    return res.ok;
  } catch {
    return false;
  } finally {
    clearTimeout(timer);
  }
}

在应用启动时 networkStatusChange 报告 connected: true,以及请求失败后执行。不要每隔几秒轮询一次,这会耗电又耗流量。确保健康检查端点在 CORS 中允许你的应用 Origin(capacitor://localhost 在 iOS 上, https://localhost 在 Android 上默认如此),或使用 CapacitorHttp 从原生 code 发送。

通过网络诊断获取更详细的状态

@capgo/capacitor-network-diagnostics 读取更多操作系统信息,并可运行绕过 WebView CORS 的原生检查:

bun add @capgo/capacitor-network-diagnostics
bunx cap sync
import { NetworkDiagnostics } from '@capgo/capacitor-network-diagnostics';

const status = await NetworkDiagnostics.getNetworkStatus();
// {
//   connected: true,
//   connectionType: 'wifi' | 'cellular' | 'ethernet' | 'vpn' | 'other' | 'unknown' | 'none',
//   internetReachable: true,
//   expensive: false,      // metered / cellular
//   constrained: false,    // iOS Low Data Mode
//   captivePortal: false,  // Android only
//   details: { ... }
// }

字段如何映射到操作系统:

字段 Android iOS
internetReachable NET_CAPABILITY_VALIDATED (Android 已进行联网探测) 与……一致 NWPathMonitor 路径状态
expensive isActiveNetworkMetered() NWPath.isExpensive
constrained 始终 false NWPath.isConstrained (低数据模式)
captivePortal NET_CAPABILITY_CAPTIVE_PORTAL 未报告
vpn 类型 是 是

在 Android 上, internetReachable 是一个强信号,因为操作系统已经测试过连接。在 iOS 上,它更接近于 connected,因此对于关键流程,请保留 HTTP 检查。

原生网络可达性检查

const api = await NetworkDiagnostics.testUrl({
  url: 'https://api.example.com/health',
  method: 'HEAD',
  timeoutMs: 4000,
});
// api.reachable, api.statusCode, api.durationMs, api.errorCode

const socket = await NetworkDiagnostics.testWebSocket({ url: 'wss://realtime.example.com' });
const port = await NetworkDiagnostics.testPort({ host: 'mqtt.example.com', port: 8883 });

原生请求不受 CORS 限制,因此你也可以测试第三方主机。

支持用的“网络报告”界面

当用户反馈“应用无法使用”时,一键生成的诊断报告能省去数小时的反复沟通:

const report = await NetworkDiagnostics.runDiagnostics({
  urls: [{ url: 'https://api.example.com/health' }, { url: 'https://cdn.example.com/ping' }],
  websockets: [{ url: 'wss://realtime.example.com' }],
  download: { url: 'https://cdn.example.com/1mb.bin', maxBytes: 1_048_576 },
  packetLoss: { url: 'https://api.example.com/health', count: 5 },
});

console.log(report.issues);        // human-readable list of problems
console.log(report.download?.mbps);
console.log(report.packetLoss?.lossPercent);

把 JSON 附到支持工单里,马上就能看出问题出在 DNS、被拦截的 WebSocket、强制门户,还是链路太慢。

适配按流量计费与低数据模式下的连接

在移动流量昂贵的市场,尊重流量限制是提升评分的捷径:

export async function shouldPrefetchMedia() {
  const s = await NetworkDiagnostics.getNetworkStatus();
  if (!s.connected) return false;
  if (s.constrained) return false; // user asked iOS to save data
  if (s.expensive) return false;   // cellular or metered Wi-Fi
  return true;
}

用它来决定视频自动播放、图片质量、后台同步和大文件下载。在 Android 上,用户可以把 Wi-Fi 网络标记为按流量计费,所以别默认 Wi-Fi 就是免费的。

构建离线横幅

比起请求失败,显示一个小横幅更直观。使用上面的 Hook:

export function OfflineBanner() {
  const { connected } = useNetworkStatus();
  if (connected) return null;
  return (
    <div role="status" aria-live="polite" className="offline-banner">
      You are offline. Changes will sync when you reconnect.
    </div>
  );
}

role="status" 配合 aria-live="polite" 让屏幕阅读器播报这一变化。针对特定框架的 UI,请参阅我们的指南: 在 Vue、Angular 和 React 中构建离线界面.

离线时排队处理任务

检测只是离线支持的一半。当状态变为离线时:

  1. 读取操作走本地存储。像 @capgo/capacitor-fast-sql 这样的 SQLite 数据库处理大数据集比 localStorage.
  2. 把用户操作连同时间戳和幂等键写入 outbox 表。
  3. 当 networkStatusChange 变为已连接时,运行 canReachApi(),然后按顺序清空发件箱。
  4. 使用指数退避重试失败的请求,间隔上限为几分钟。

对于必须在应用关闭后仍能完成的上传,应交给原生上传器处理,而不是 fetch.

Web 回退方案

@capacitor/network 同样在浏览器中运行,它基于 navigator.onLine 以及 online 以及 offline 事件。这意味着你的 code 可在 bun run dev 以及在 PWA 中无需改动,但 connectionType 通常会 unknown 在 Web 端。Chrome 的 navigator.connection 提供 effectiveType 以及 saveData,Safari 不支持,因此请将其视为可选提示。

故障排除

监听器在 iOS 模拟器上始终不触发:模拟器共享 Mac 的网络。请在 Mac 上切换 Wi-Fi,或在开启飞行模式的设备上测试。

connectionType 为 unknown 使用 VPN 时:官方插件仅报告 wifi, cellular, none 以及 unknown。请使用诊断插件,它会返回 vpn 以及 ethernet.

切换网络时,应用会显示离线一秒:如上所示,对离线状态切换做防抖处理。

健康检查在设备上失败,但在浏览器中正常: CORS. 允许 capacitor://localhost 以及 https://localhost,或使用 NetworkDiagnostics.testUrl,它以原生方式运行。

navigator.onLine 为 true,而插件显示离线:信任该插件。部分 Android WebView 更新起来并不 navigator.onLine 可靠。

通过 OTA 发布离线修复

离线处理主要靠 JavaScript:阈值、横幅、重试逻辑、缓存规则。借助 Capgo 实时更新,你可以当天为每位用户调整这些设置。Capgo 会在后台下载更新 bundle,下载失败时保留当前版本,因此网络再差也不会让用户卡在装了一半的更新上。 网络诊断文档 该命令会列出所有选项。

为 Capacitor 应用提供 Live Updates

当 Web 层出现线上 Bug 时,通过 Capgo 发布修复,而不用等 App Store 审核好几天。用户会在后台收到更新,原生变更则仍走正常审核流程。

博客最新动态

Capgo 为你提供打造真正专业的移动应用所需的最有价值的洞察。