要在 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 中构建离线界面.
离线时排队处理任务
检测只是离线支持的一半。当状态变为离线时:
- 读取操作走本地存储。像
@capgo/capacitor-fast-sql这样的 SQLite 数据库处理大数据集比localStorage. - 把用户操作连同时间戳和幂等键写入 outbox 表。
- 当
networkStatusChange变为已连接时,运行canReachApi(),然后按顺序清空发件箱。 - 使用指数退避重试失败的请求,间隔上限为几分钟。
对于必须在应用关闭后仍能完成的上传,应交给原生上传器处理,而不是 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,下载失败时保留当前版本,因此网络再差也不会让用户卡在装了一半的更新上。 网络诊断文档 该命令会列出所有选项。