跳转至正文

如何在 Capacitor App 中检测网络状态

在 Capacitor App 中检测网络状态:读取在线/离线状态、监听变化、验证是否真正能访问互联网,并处理按流量计费的网络。

如何在 Capacitor App 中检测网络状态

要在 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 需要请求或 OS 验证
我应该避免大文件下载吗? expensive, constrained 计费网络与 iOS 低数据模式

大多数离线 Bug 都源于误判。例如,带有登录页面的酒店 Wi-Fi 会报告已连接, connected: true 而每一次 API 调用都会超时。

安装 Network 插件

bun add @capacitor/network
bunx cap sync

无需任何配置。在 Android 上,该插件会将 ACCESS_NETWORK_STATE 添加到合并后的 manifest 中,这是一种安装时授予的常规权限。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'

在应用启动时调用它,以决定是从缓存还是网络加载数据。它会在几毫秒内返回,因为它直接读取 OS 状态,不会产生网络流量。

监听网络变化

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 该状态来自操作系统的网络栈。在以下情况下,该状态依然成立:

  • 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以及请求失败后运行。不要每隔几秒轮询一次,这会耗尽电量和流量。确保 health endpoint 在 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 限制,因此你也可以测试第三方主机。

支持“网络报告”界面

当用户说“App 无法运行”时,一键生成的诊断报告可以节省数小时的沟通成本:

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 被拦截、强制门户(captive portal)还是链路延迟。

适配按流量计费和低数据模式连接

在移动数据费用较高的市场,尊重用户的数据限制是提升应用评分的捷径:

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. 从本地存储读取数据。使用 SQLite 数据库等工具, @capgo/capacitor-fast-sql 处理大数据集的效果比 localStorage.
  2. 将用户操作连同时间戳和幂等键一起写入 outbox 表。
  3. 在 networkStatusChange 恢复连接时,请运行 canReachApi(),然后按顺序清空发件箱。
  4. 使用指数退避算法重试失败的请求,最大间隔为几分钟。

对于必须在应用关闭后仍能完成的上传任务,应交给 native uploader 处理,而不是 fetch.

Web 回退

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

故障排除

监听器在 iOS Simulator 上永远不会触发:模拟器共享 Mac 的网络。请切换 Mac 的 Wi-Fi 或在开启飞行模式的真机上进行测试。

connectionType 是 unknown 正在使用 VPN:官方插件仅报告 wifi, cellular, none 和 unknown。请使用 diagnostics 插件,它会返回 vpn 和 ethernet.

切换网络时,App 会短暂显示为离线状态:如上所示,对离线状态切换进行防抖处理。

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

navigator.onLine 当插件显示离线时,其值为 true:信任插件。某些 Android WebView 不会 navigator.onLine 可靠地更新。

通过 OTA 推送离线修复方案

离线处理主要通过 JavaScript 实现:阈值、横幅、重试逻辑、缓存规则。配合 Capgo Live Update, 你可以在当天为每个用户进行微调。Capgo 会在后台下载 update bundles,如果下载失败则保留当前版本,因此糟糕的网络连接绝不会导致用户处于更新了一半的状态。此外, 网络诊断文档 列出了所有选项。

为 Capacitor 应用提供实时更新

当 Web 层 Bug 已上线时,通过 Capgo 推送修复,不必等 App Store 审核数天。用户会在后台获取更新,而原生变更仍走常规审核流程。

博客最新文章

Capgo 为你提供打造真正专业移动应用所需的深度洞察。