zero_network_kit 全部公开功能的使用示例,每个代码块都可以直接复制运行。
English | 简体中文
注意:本插件的
NetworkType只有label与id,没有displayName。 请写connection.type.label,而不是connection.type.displayName。
| # | 能力 | API |
|---|---|---|
| 0 | 引入与初始化 | ZeroNetworkKit.init() |
| 1 | 该用哪个 API? | — |
| 2 | 全局配置 | NetworkDiagnosticConfig |
| 3 | 连通性 | checkConnection() |
| 4 | 连通性监听 | onConnectivityChanged |
| 5 | 延迟探测 | ping() |
| 6 | DNS 解析 | resolve() |
| 7 | 端口检测 | checkPort() / scanPorts() |
| 8 | 测速 | runSpeedTest() |
| 9 | 质量评分 | evaluateQuality() |
| 10 | 汇总报告 | diagnose() |
| 11 | 微基准测试 | NetworkBenchmark.runAll() |
| 12 | 原生平台信息 | getPlatformVersion() |
| 13 | 序列化 | toMap() |
| 14 | 注入与测试 | configure() |
| 15 | 模型字段速查 | — |
| 16 | 常见陷阱 | — |
import 'package:zero_network_kit/zero_network_kit.dart';一个 import 就能拿到全部能力。初始化是可选的——不调用也能直接用(走内置默认值)。
void main() {
// 可选:全局定制一次默认参数。
ZeroNetworkKit.init(
config: const NetworkDiagnosticConfig(pingHost: '1.1.1.1'),
);
runApp(const MyApp());
}应用退出时释放插件自己持有的 HTTP 客户端:
await ZeroNetworkKit.dispose();查看当前状态:
ZeroNetworkKit.isInitialized; // 未调用 init() 时为 false
ZeroNetworkKit.config; // 当前生效的 NetworkDiagnosticConfig| 我想知道…… | 调用 |
|---|---|
| 我在线吗?走的什么链路? | NetworkDiagnostic.checkConnection() |
| 监听 Wi-Fi ⇄ 蜂窝 切换 | NetworkDiagnostic.onConnectivityChanged |
| 延迟、抖动、丢包 | NetworkDiagnostic.ping() |
| DNS 慢还是坏了? | NetworkDiagnostic.resolve() |
host:port 通不通? |
NetworkDiagnostic.checkPort() |
| 这批端口哪些开着? | NetworkDiagnostic.scanPorts() |
| 上下行有多快? | NetworkDiagnostic.runSpeedTest() |
| 一句话结论「好还是不好」 | NetworkDiagnostic.evaluateQuality() |
| 一次跑完全部并汇总 | NetworkDiagnostic.diagnose() |
| 这套诊断 API 本身贵不贵? | NetworkBenchmark.runAll() |
| 原生版本 / SSID / 网关 / MAC | ZeroNetworkKit.getPlatformVersion()、getNativeNetworkDetails() |
NetworkDiagnosticConfig 集中了全部默认参数,只需覆盖你关心的字段,其余保持默认。
ZeroNetworkKit.init(
config: const NetworkDiagnosticConfig(
pingHost: '1.1.1.1',
pingPort: 443,
pingCount: 5,
dnsDomain: 'example.com',
dnsServers: <String>['1.1.1.1', '8.8.8.8'],
downloadUrl: 'https://my-cdn.example.com/speedtest.bin',
uploadUrl: 'https://my-cdn.example.com/upload',
),
);| 字段 | 默认值 | 含义 |
|---|---|---|
pingHost |
'1.1.1.1' |
默认 Ping / 端口扫描目标 |
pingPort |
443 |
TCP Ping 使用的端口 |
pingCount |
4 |
每次 Ping 的探测次数 |
pingTimeout |
3s |
单次探测超时 |
pingInterval |
200ms |
两次探测之间的间隔 |
dnsDomain |
'www.google.com' |
默认 DNS 测试域名 |
dnsServers |
['1.1.1.1', '8.8.8.8', '114.114.114.114'] |
默认查询的 DNS 服务器 |
dnsTimeout |
5s |
单台服务器查询超时 |
downloadUrl |
Cloudflare __down?bytes=25000000 |
下载测速端点 |
uploadUrl |
Cloudflare __up |
上传测速端点 |
uploadPayloadBytes |
1048576(1 MiB) |
上传负载大小 |
speedTestTimeout |
30s |
单请求超时 |
speedTestMaxDuration |
10s |
每个阶段的采样时长 |
portCheckTimeout |
3s |
单端口检测超时 |
probePorts |
[80, 443] |
scanPorts() 默认扫描的端口 |
qualityTargets |
const QualityTargets() |
质量评分理想值 |
默认测速端点是 Cloudflare 的公共服务,正式项目请替换为自建端点。
const NetworkDiagnosticConfig(
qualityTargets: QualityTargets(
excellentLatency: 30, // ms
acceptableLatency: 150, // ms
excellentJitter: 5, // ms
acceptableJitter: 40, // ms
acceptablePacketLoss: 5, // %
excellentDownload: 50, // Mbps
acceptableDownload: 5, // Mbps
excellentUpload: 20, // Mbps
acceptableUpload: 2, // Mbps
excellentDns: 30, // ms
acceptableDns: 200, // ms
),
);final connection = await NetworkDiagnostic.checkConnection();
print('类型 : ${connection.type.label}'); // Wi-Fi / Mobile / Ethernet …
print('已连接 : ${connection.isConnected}');
print('IPv4 : ${connection.ipAddress}');
print('IPv6 : ${connection.ipv6Address}');
print('网关 : ${connection.gateway}');
print('SSID : ${connection.ssid}');
print('信号 : ${connection.signalStrength} dBm');
print('MAC : ${connection.macAddress}');
print('VPN : ${connection.isVpn}');
print('采样时间 : ${connection.timestamp}');参数:
| 参数 | 默认值 | 含义 |
|---|---|---|
includeNativeDetails |
true |
是否走原生通道读取 SSID / 网关 / MAC / VPN |
probeReachability |
false |
是否额外发一次真实请求填充 isReachable |
probeTimeout |
3s |
上述可达性探测的超时 |
// 跳过原生通道(最省,适合放在热路径上)。
final quick = await NetworkDiagnostic.checkConnection(
includeNativeDetails: false,
);
// 确认「真的能上外网」,而不只是网卡亮着。
final verified = await NetworkDiagnostic.checkConnection(
probeReachability: true,
probeTimeout: const Duration(seconds: 5),
);
if (verified.isReachable == false) {
print('网卡是通的,但外网不可达。');
}如果只需要「在不在線、走什么網络、IP 是多少、切换时通知」,只用第 3、4 节即可——
无需 init(),也不必调用 ping / DNS / 测速。
一次性快照(纯 Dart,无需 Widget):
final c = await NetworkDiagnostic.checkConnection();
print('${c.type.label} · 在线=${c.isConnected} · IP=${c.ipAddress}');一个最小的 Flutter 页面,展示当前连接并在切换时刷新(Wi-Fi ⇄ 蜂窝等):
import 'package:flutter/material.dart';
import 'package:zero_network_kit/zero_network_kit.dart';
class ConnectivityScreen extends StatelessWidget {
const ConnectivityScreen({super.key});
@override
Widget build(BuildContext context) {
return StreamBuilder<NetworkConnectionInfo>(
stream: NetworkDiagnostic.onConnectivityChanged,
builder: (context, snap) {
final c = snap.data;
if (c == null) {
return const Center(child: CircularProgressIndicator());
}
return ListView(
children: [
ListTile(title: const Text('类型'), trailing: Text(c.type.label)),
ListTile(title: const Text('在线'), trailing: Text('${c.isConnected}')),
ListTile(title: const Text('IPv4'), trailing: Text(c.ipAddress ?? '—')),
ListTile(title: const Text('IPv6'), trailing: Text(c.ipv6Address ?? '—')),
],
);
},
);
}
}
ZeroNetworkKit.init()是可选的,仅在想覆盖默认参数时才需要;单用连通性直接走内置默认值即可。
final subscription = NetworkDiagnostic.onConnectivityChanged.listen(
(info) => print('当前网络 ${info.type.id} · ${info.ipAddress}'),
);
// 之后取消:
await subscription.cancel();在 Widget 里:
StreamBuilder<NetworkConnectionInfo>(
stream: NetworkDiagnostic.onConnectivityChanged,
builder: (context, snapshot) {
final info = snapshot.data;
if (info == null) return const Text('检测中…');
return Text('${info.type.label} · ${info.isConnected}');
},
)final ping = await NetworkDiagnostic.ping(
host: '1.1.1.1',
count: 5,
timeout: const Duration(seconds: 2),
interval: const Duration(milliseconds: 200),
port: 443,
);
print('收到 : ${ping.received}/${ping.sent}');
print('丢包 : ${ping.packetLoss.toStringAsFixed(1)} %');
print('最小/平均/最大: ${ping.minTime.toStringAsFixed(1)} / '
'${ping.averageTime.toStringAsFixed(1)} / '
'${ping.maxTime.toStringAsFixed(1)} ms');
print('抖动 : ${ping.jitter.toStringAsFixed(2)} ms');
print('样本 : ${ping.times}');| 参数 | 默认值 | 含义 |
|---|---|---|
host |
config.pingHost |
目标主机 |
count |
config.pingCount |
探测次数 |
timeout |
config.pingTimeout |
单次探测超时 |
interval |
config.pingInterval |
探测间隔 |
port |
config.pingPort |
PingMode.tcp 下探测的端口 |
mode |
PingMode.tcp |
PingMode.tcp 或 PingMode.icmp |
PingMode.tcp 通过 TCP 握手到 host:port 测量往返,是 ICMP 的可移植等价物,
也是 Android / iOS 上唯一可用的方式。
// 仅桌面端可用;`ping` 命令不存在时会优雅降级。
await NetworkDiagnostic.ping(host: '1.1.1.1', count: 4, mode: PingMode.icmp);至少一次收到响应时 ping.isSuccess 才为 true,读平均值前建议先判断它。
final results = await NetworkDiagnostic.resolve(
domain: 'example.com',
dnsServers: const <String>['1.1.1.1', '8.8.8.8', '114.114.114.114'],
timeout: const Duration(seconds: 5),
concurrent: true,
includeSystemResolver: true,
);
for (final r in results) {
if (r.isSuccess) {
print('${r.server.padRight(16)} → ${r.resolvedIps.join(", ")} '
'(${r.responseTimeMs.toStringAsFixed(1)} ms)');
} else {
print('${r.server.padRight(16)} ✗ ${r.errorMessage}');
}
}| 参数 | 默认值 | 含义 |
|---|---|---|
domain |
config.dnsDomain |
待解析域名 |
dnsServers |
config.dnsServers |
依次/并发查询的服务器列表 |
timeout |
config.dnsTimeout |
单台服务器查询超时 |
concurrent |
true |
是否并发查询全部服务器 |
includeSystemResolver |
false |
是否追加一行 system |
说明:
server == 'system'的行来自系统解析器;其余是对指定 IP 的原始 UDP 查询, 由内置的纯 DartDnsPacket编解码。includeSystemResolver在门面层默认为false,需要系统解析器时请显式传true。
// 挑出最快的一台解析器。
final fastest = results
.where((r) => r.isSuccess)
.reduce((a, b) => a.responseTimeMs <= b.responseTimeMs ? a : b);
print('最快解析器:${fastest.server}');// 单端口 → 布尔便捷
final open = await NetworkDiagnostic.isPortOpen(
host: 'example.com',
port: 443,
timeout: const Duration(seconds: 3),
);
print(open ? 'HTTPS 可达' : 'HTTPS 不可达');// 多端口 → 每条结果都带 RTT 与失败原因
final scan = await NetworkDiagnostic.scanPorts(
host: 'example.com',
ports: const <int>[22, 80, 443, 8080, 8443],
concurrency: 8,
);
for (final r in scan) {
print('${r.host}:${r.port} '
'${r.isOpen ? "开放" : "关闭"} '
'${r.responseTimeMs.toStringAsFixed(1)} ms '
'${r.errorMessage ?? ""}');
}| 参数 | 默认值 | 含义 |
|---|---|---|
ports(scanPorts) |
config.probePorts([80, 443]) |
待探测端口 |
concurrency |
12 |
最大并发连接数 |
timeout |
config.portCheckTimeout |
单端口超时 |
checkPort()返回PortCheckResult(含isOpen、responseTimeMs、errorMessage);单端口布尔诉求请用isPortOpen()。需要 RTT 或失败原因时 可直接调用checkPort(),或给scanPorts(host: 'example.com', ports: [443])取单条结果。
final speed = await NetworkDiagnostic.runSpeedTest(
includeUpload: true,
includePing: true,
onProgress: (progress) {
print('${progress.phase.name}: '
'${progress.speedMbps.toStringAsFixed(1)} Mbps '
'(${progress.bytes} 字节,${progress.elapsed.inMilliseconds} ms)');
},
);
print('下载 : ${speed.downloadSpeed.toStringAsFixed(2)} Mbps');
print('上传 : ${speed.uploadSpeed.toStringAsFixed(2)} Mbps');
print('延迟 : ${speed.ping.toStringAsFixed(1)} ms');
print('抖动 : ${speed.jitter.toStringAsFixed(2)} ms');
print('丢包 : ${speed.packetLoss.toStringAsFixed(1)} %');
print('服务端 : ${speed.server}');
print('总耗时 : ${speed.duration.inMilliseconds} ms');| 参数 | 默认值 | 含义 |
|---|---|---|
downloadUrl / uploadUrl |
来自 config | 使用的端点 |
timeout |
config.speedTestTimeout |
单请求超时 |
maxDuration |
config.speedTestMaxDuration |
每阶段采样时长 |
uploadPayloadBytes |
config.uploadPayloadBytes |
上传大小 |
pingHost / pingCount |
来自 config | 测速期间采样的延迟 |
includeUpload |
true |
是否跳过上传阶段 |
includePing |
true |
是否跳过延迟采样 |
onProgress |
null |
两个阶段中都会被反复调用 |
进度阶段依次为 SpeedTestPhase.download → SpeedTestPhase.upload →
SpeedTestPhase.completed。
轻量低流量版本(跳过上传与延迟):
final quick = await NetworkDiagnostic.runSpeedTest(
includeUpload: false,
includePing: false,
maxDuration: const Duration(seconds: 5),
);final quality = await NetworkDiagnostic.evaluateQuality(
includePing: true,
includeDns: true,
includeSpeedTest: true,
includeUpload: true,
);
print('${quality.score.toStringAsFixed(1)}/100 —— ${quality.level.label}');
quality.metrics.forEach((metric, value) {
print(' $metric = ${value.toStringAsFixed(2)}');
});
for (final suggestion in quality.suggestions) {
print('• $suggestion');
}| 参数 | 默认值 | 含义 |
|---|---|---|
includePing |
true |
采样延迟 / 抖动 / 丢包 |
includeDns |
true |
采样 DNS 耗时 |
includeSpeedTest |
true |
采样上下行(流量较大) |
includeUpload |
true |
是否计入上传指标 |
pingHost / pingCount |
来自 config | 延迟目标 |
dnsDomain / dnsServers |
来自 config | DNS 目标 |
downloadUrl / uploadUrl |
来自 config | 带宽端点 |
不消耗流量的轻量版本:
final quality = await NetworkDiagnostic.evaluateQuality(includeSpeedTest: false);要调整评分理想值,只能通过全局配置(evaluateQuality 没有单次调用的目标参数):
ZeroNetworkKit.init(
config: const NetworkDiagnosticConfig(
qualityTargets: QualityTargets(excellentLatency: 20, acceptableLatency: 100),
),
);如果指标已经在手,可以完全不走网络,直接用纯函数打分:
final score = NetworkQualityEvaluator.evaluate(
latency: 42,
jitter: 6,
packetLoss: 0,
download: 88.4,
upload: 12.1,
dns: 25,
signalStrength: -55,
targets: const QualityTargets(),
);
print('${score.score.toStringAsFixed(1)} → ${score.level.label}');
print(score.suggestions);等级阈值:≥90 极佳 · ≥75 良好 · ≥60 一般 · ≥40 较差 · 其余为极差
(见 NetworkQualityLevel.fromScore)。
权重分配(只对「可用的指标」加权平均):
quality.metrics 中的键 |
权重 | 来源 |
|---|---|---|
latency |
0.25 | PingResult.averageTime |
jitter |
0.10 | PingResult.jitter |
packetLoss |
0.15 | PingResult.packetLoss |
download |
0.25 | SpeedTestResult.downloadSpeed |
upload |
0.15 | SpeedTestResult.uploadSpeed |
dns |
0.10 | 成功的 DnsTestResult.responseTimeMs 均值 |
signalStrength |
0.10 | NetworkConnectionInfo.signalStrength |
final report = await NetworkDiagnostic.diagnose(
includePing: true,
includeDns: true,
includePorts: true,
includeSpeedTest: true,
includeUpload: true,
host: '1.1.1.1',
dnsDomain: 'example.com',
dnsServers: const <String>['1.1.1.1', '8.8.8.8'],
ports: const <int>[80, 443],
);
print(report); // 一行摘要
print(report.connection.type.label);
print(report.ping?.averageTime);
print(report.dnsResults.length);
print(report.portResults.where((p) => p.isOpen).length);
print(report.speedTest?.downloadSpeed);
print(report.quality.level.label);includePorts 默认是 false,默认调用不会扫端口。任何子项失败都不会中断整体
流程,对应字段保持空。
final suite = await NetworkBenchmark.runAll(
iterations: 20,
warmupIterations: 3,
host: '1.1.1.1',
dnsDomain: 'example.com',
port: 443,
);
for (final result in suite.results) {
print('${result.testName.padRight(12)} '
'平均 ${(result.averageDuration.inMicroseconds / 1000).toStringAsFixed(2)} ms '
'± ${(result.standardDeviation.inMicroseconds / 1000).toStringAsFixed(2)} ms '
'${result.operationsPerSecond.toStringAsFixed(1)} 次/秒 '
'失败=${result.failures}');
}
// 按名称取单项:
print(suite['ping']?.averageDuration);
print(suite.totalDuration);单项基准:
await NetworkBenchmark.benchmarkConnection(iterations: 20);
await NetworkBenchmark.benchmarkPing(iterations: 20, host: '1.1.1.1');
await NetworkBenchmark.benchmarkDns(iterations: 20, domain: 'example.com');
await NetworkBenchmark.benchmarkPortCheck(iterations: 20, port: 443);
await NetworkBenchmark.benchmarkPlatformChannel(iterations: 20);用它来判断某项诊断能否放在启动路径上。
final version = await ZeroNetworkKit.getPlatformVersion();
print(version); // 例如 'Android 14' / 'iOS 18.0'
final details = await ZeroNetworkKit.getNativeNetworkDetails();
if (details != null) {
print(details); // SSID、BSSID、网关、MAC、VPN 标志、RSSI 等
}NetworkDiagnostic 上也有同名便捷方法:
await NetworkDiagnostic.getPlatformVersion();
await NetworkDiagnostic.getNativeNetworkDetails();平台没有该数据或缺少权限时返回 null(或部分字段缺失的 Map),不会抛异常。
所有结果对象都实现了 toMap(),可直接 JSON 编码:
import 'dart:convert';
final report = await NetworkDiagnostic.diagnose(includeSpeedTest: false);
final json = jsonEncode(report.toMap());
print(json);
// 连接快照可反序列化。
final decoded = NetworkConnectionInfo.fromMap(jsonDecode(jsonEncode(
report.connection.toMap(),
)) as Map<Object?, Object?>);| 类型 | 序列化方式 |
|---|---|
NetworkConnectionInfo |
toMap() + fromMap() |
PingResult |
toMap() |
DnsTestResult |
toMap() |
PortCheckResult |
toMap() |
SpeedTestResult |
toMap() |
NetworkQualityScore |
toMap() |
NetworkDiagnosticReport |
toMap() |
BenchmarkResult / BenchmarkSuiteResult |
toMap() |
每个服务都支持注入协作对象,集成点可以完全脱离网络做单元测试:
class FakeConnectivityAdapter implements ConnectivityAdapter {
@override
Future<List<String>> checkConnectivity() async => <String>['wifi'];
@override
Stream<List<String>> get onConnectivityChanged => const Stream.empty();
}
NetworkDiagnostic.configure(
config: const NetworkDiagnosticConfig(pingHost: '127.0.0.1'),
connectivity: ConnectivityService(adapter: FakeConnectivityAdapter()),
ping: PingService(),
dns: DnsService(),
ports: PortService(),
speedTest: SpeedTestService(client: fakeClient),
);
// 用完恢复默认实现。
NetworkDiagnostic.reset();也可以直接替换单个服务:
NetworkDiagnostic.pingService = MyFakePingService();
NetworkDiagnostic.dnsService = MyFakeDnsService();| 成员 | 说明 |
|---|---|
| 枚举值 | none、wifi、mobile、ethernet、vpn、bluetooth、other |
label |
人类可读名称:'Wi-Fi'、'Mobile'…… |
id |
稳定的英文标识(即 name),适合持久化 |
isConnected |
除 none 外均为 true |
NetworkType.fromRaw(Object?) |
解析平台返回的原始字符串 |
isConnected、type、ssid、signalStrength(dBm)、ipAddress、
ipv6Address、gateway、macAddress、isVpn、isReachable、timestamp,
以及 copyWith({bool? isReachable})。
字段 host、port、mode、sent、received、times、timestamp;
派生 lost、packetLoss(%)、minTime、maxTime、averageTime、
jitter、isSuccess。
字段 server、domain、isSuccess、responseTime、resolvedIps、
errorMessage、timestamp;派生 responseTimeMs、primaryAddress。
字段 host、port、isOpen、responseTime、errorMessage、timestamp;
派生 responseTimeMs。
字段 downloadSpeed、uploadSpeed(Mbps)、ping(ms)、jitter(ms)、
packetLoss(%)、downloadedBytes、uploadedBytes、downloadDuration、
uploadDuration、server、timestamp;派生 duration;静态方法
SpeedTestResult.mbpsFromBytes(bytes, elapsed)。
phase(download、upload、completed)、bytes、elapsed、speedMbps。
score(0–100)、level、metrics(Map<String, double>)、suggestions、
timestamp;NetworkQualityLevel.fromScore(double)、level.label。
connection、ping、dnsResults、portResults、speedTest、quality、
timestamp。
BenchmarkResult:testName、iterations、totalDuration、
averageDuration、minDuration、maxDuration、standardDeviation、
operationsPerSecond、failures、timestamp。
BenchmarkSuiteResult:suiteName、results、totalDuration、timestamp,
以及 suite['名称']。
另有构造辅助 BenchmarkResult.fromSamples(...)。
- 是
type.label,不是displayName。NetworkType只暴露label/id。 checkPort()返回bool。 需要 RTT 或失败原因时用scanPorts(..., ports: [端口])。includeSystemResolver默认false。 需要system那一行时要显式传true。- TCP Ping 需要有端口在监听。 用
port: 443探测一台不开放 TCP/443 的主机 会失败——那是「被过滤」,不是「离线」。桌面端可用mode: PingMode.icmp更接近 真实 ICMP。 PingMode.icmp仅桌面端可用,且ping命令不存在时会静默降级。- 测速会产生真实流量(默认约 25 MB 下载)。在计费网络上请先征得用户同意。
- 缺权限是降级,不是抛异常。 缺定位权限时
ssid == null,其余字段照常返回。 diagnose()的includePorts默认false。init()可选,但dispose()只关闭插件自己创建的客户端——你注入的httpClient不会被关闭,需要自行处理。