网络质量评估:HarmonyOS PC开发中带宽与延迟检测
知己知彼,百战不殆------了解网络质量才能优化用户体验
一、为什么需要网络质量评估?
知道"有网"和"没网"只是第一步,更精细的问题是:网络有多好?
同样的"有网"状态,WiFi千兆光纤和电梯里的微弱信号,用户体验天差地别。如果应用能感知网络质量,就能:
智能降级:弱网环境下降低图片质量、减少请求频率、关闭实时推送,避免用户等待超时。
预加载决策:网络好时提前加载下一页数据、预缓存视频,网络差时暂停预加载节省流量。
故障预警:网络质量持续下降时,提前提示用户可能遇到问题,而不是等到请求失败才发现。
性能优化:根据网络质量选择最优CDN节点、调整请求并发数、选择合适的协议。
用户体验报告:收集用户的网络质量数据,分析问题原因,优化产品策略。
鸿蒙系统提供了网络质量评估API,结合自定义检测方案,可以全面评估网络质量。
二、核心原理:网络质量指标体系
2.1 核心质量指标
#mermaid-svg-f20TpanFhtZEzUZU{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-f20TpanFhtZEzUZU .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-f20TpanFhtZEzUZU .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-f20TpanFhtZEzUZU .error-icon{fill:#552222;}#mermaid-svg-f20TpanFhtZEzUZU .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-f20TpanFhtZEzUZU .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-f20TpanFhtZEzUZU .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-f20TpanFhtZEzUZU .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-f20TpanFhtZEzUZU .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-f20TpanFhtZEzUZU .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-f20TpanFhtZEzUZU .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-f20TpanFhtZEzUZU .marker{fill:#333333;stroke:#333333;}#mermaid-svg-f20TpanFhtZEzUZU .marker.cross{stroke:#333333;}#mermaid-svg-f20TpanFhtZEzUZU svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-f20TpanFhtZEzUZU p{margin:0;}#mermaid-svg-f20TpanFhtZEzUZU .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-f20TpanFhtZEzUZU .cluster-label text{fill:#333;}#mermaid-svg-f20TpanFhtZEzUZU .cluster-label span{color:#333;}#mermaid-svg-f20TpanFhtZEzUZU .cluster-label span p{background-color:transparent;}#mermaid-svg-f20TpanFhtZEzUZU .label text,#mermaid-svg-f20TpanFhtZEzUZU span{fill:#333;color:#333;}#mermaid-svg-f20TpanFhtZEzUZU .node rect,#mermaid-svg-f20TpanFhtZEzUZU .node circle,#mermaid-svg-f20TpanFhtZEzUZU .node ellipse,#mermaid-svg-f20TpanFhtZEzUZU .node polygon,#mermaid-svg-f20TpanFhtZEzUZU .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-f20TpanFhtZEzUZU .rough-node .label text,#mermaid-svg-f20TpanFhtZEzUZU .node .label text,#mermaid-svg-f20TpanFhtZEzUZU .image-shape .label,#mermaid-svg-f20TpanFhtZEzUZU .icon-shape .label{text-anchor:middle;}#mermaid-svg-f20TpanFhtZEzUZU .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-f20TpanFhtZEzUZU .rough-node .label,#mermaid-svg-f20TpanFhtZEzUZU .node .label,#mermaid-svg-f20TpanFhtZEzUZU .image-shape .label,#mermaid-svg-f20TpanFhtZEzUZU .icon-shape .label{text-align:center;}#mermaid-svg-f20TpanFhtZEzUZU .node.clickable{cursor:pointer;}#mermaid-svg-f20TpanFhtZEzUZU .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-f20TpanFhtZEzUZU .arrowheadPath{fill:#333333;}#mermaid-svg-f20TpanFhtZEzUZU .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-f20TpanFhtZEzUZU .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-f20TpanFhtZEzUZU .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-f20TpanFhtZEzUZU .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-f20TpanFhtZEzUZU .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-f20TpanFhtZEzUZU .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-f20TpanFhtZEzUZU .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-f20TpanFhtZEzUZU .cluster text{fill:#333;}#mermaid-svg-f20TpanFhtZEzUZU .cluster span{color:#333;}#mermaid-svg-f20TpanFhtZEzUZU div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-f20TpanFhtZEzUZU .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-f20TpanFhtZEzUZU rect.text{fill:none;stroke-width:0;}#mermaid-svg-f20TpanFhtZEzUZU .icon-shape,#mermaid-svg-f20TpanFhtZEzUZU .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-f20TpanFhtZEzUZU .icon-shape p,#mermaid-svg-f20TpanFhtZEzUZU .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-f20TpanFhtZEzUZU .icon-shape .label rect,#mermaid-svg-f20TpanFhtZEzUZU .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-f20TpanFhtZEzUZU .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-f20TpanFhtZEzUZU .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-f20TpanFhtZEzUZU :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-f20TpanFhtZEzUZU .primary>*{fill:#4A90E2!important;stroke:#2E5C8A!important;stroke-width:2px!important;color:#fff!important;}#mermaid-svg-f20TpanFhtZEzUZU .primary span{fill:#4A90E2!important;stroke:#2E5C8A!important;stroke-width:2px!important;color:#fff!important;}#mermaid-svg-f20TpanFhtZEzUZU .primary tspan{fill:#fff!important;}#mermaid-svg-f20TpanFhtZEzUZU .warning>*{fill:#F5A623!important;stroke:#C17D10!important;stroke-width:2px!important;color:#fff!important;}#mermaid-svg-f20TpanFhtZEzUZU .warning span{fill:#F5A623!important;stroke:#C17D10!important;stroke-width:2px!important;color:#fff!important;}#mermaid-svg-f20TpanFhtZEzUZU .warning tspan{fill:#fff!important;}#mermaid-svg-f20TpanFhtZEzUZU .info>*{fill:#7ED321!important;stroke:#5BA318!important;stroke-width:2px!important;color:#fff!important;}#mermaid-svg-f20TpanFhtZEzUZU .info span{fill:#7ED321!important;stroke:#5BA318!important;stroke-width:2px!important;color:#fff!important;}#mermaid-svg-f20TpanFhtZEzUZU .info tspan{fill:#fff!important;} 网络质量评估
带宽指标
延迟指标
稳定性指标
下行带宽
下载速度
上行带宽
上传速度
可用带宽
当前可利用带宽
RTT往返时延
请求响应时间
DNS解析时延
域名解析时间
TCP连接时延
握手时间
丢包率
数据包丢失比例
抖动
延迟波动幅度
重传率
需要重传的比例
2.2 质量等级划分
| 等级 | 带宽 | 延迟 | 丢包率 | 用户体验 |
|---|---|---|---|---|
| 优秀 | >10Mbps | <50ms | <0.1% | 流畅无感知 |
| 良好 | 5-10Mbps | 50-100ms | 0.1-0.5% | 基本流畅 |
| 一般 | 1-5Mbps | 100-200ms | 0.5-1% | 可接受 |
| 较差 | 0.5-1Mbps | 200-500ms | 1-3% | 明显卡顿 |
| 很差 | <0.5Mbps | >500ms | >3% | 几乎不可用 |
2.3 检测方法对比
| 方法 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 下载测速 | 下载已知大小文件,计算速度 | 直观准确 | 消耗流量 | 初始评估 |
| Ping检测 | 发送小包测量RTT | 开销小 | 可能被限制 | 持续监测 |
| 请求采样 | 记录实际请求的性能 | 真实数据 | 需要积累 | 日常运行 |
| 系统API | 获取系统网络信息 | 无额外开销 | 信息有限 | 快速评估 |
三、代码实战:三种检测方案
场景一:带宽测速实现
通过下载测试文件测量实际带宽。
typescript
import http from '@ohos.net.http';
import { BusinessError } from '@ohos.base';
// 测速结果
interface SpeedTestResult {
downloadSpeed: number; // 下载速度(KB/s)
uploadSpeed: number; // 上传速度(KB/s)
latency: number; // 延迟(ms)
jitter: number; // 抖动(ms)
timestamp: number; // 测试时间
testDuration: number; // 测试耗时(ms)
}
// 测速配置
interface SpeedTestConfig {
testUrl: string; // 测试文件URL
testSize: number; // 测试文件大小(字节)
timeout: number; // 超时时间(ms)
samples: number; // 采样次数
warmupSamples: number; // 预热次数(不计入结果)
}
// 默认配置
const DEFAULT_SPEED_TEST_CONFIG: SpeedTestConfig = {
testUrl: 'https://speed.example.com/test.bin',
testSize: 1024 * 1024, // 1MB
timeout: 30000,
samples: 5,
warmupSamples: 2
};
// 带宽测速器
class BandwidthTester {
private config: SpeedTestConfig;
// 回调
private onProgress: ((progress: number, speed: number) => void) | null = null;
constructor(config: Partial<SpeedTestConfig> = {}) {
this.config = { ...DEFAULT_SPEED_TEST_CONFIG, ...config };
}
// 执行测速
async runTest(): Promise<SpeedTestResult> {
console.info('开始带宽测速');
let startTime = Date.now();
// 1. 预热
console.info('预热测试...');
for (let i = 0; i < this.config.warmupSamples; i++) {
await this.downloadTestFile();
}
// 2. 正式测试
let speeds: number[] = [];
let latencies: number[] = [];
for (let i = 0; i < this.config.samples; i++) {
let result = await this.downloadTestFile();
speeds.push(result.speed);
latencies.push(result.latency);
// 通知进度
let progress = (i + 1) / this.config.samples;
let avgSpeed = this.average(speeds);
this.onProgress?.(progress, avgSpeed);
console.info(`测试 ${i + 1}/${this.config.samples}: ${result.speed.toFixed(2)} KB/s, ${result.latency.toFixed(2)} ms`);
}
// 3. 计算结果
let downloadSpeed = this.average(speeds);
let latency = this.average(latencies);
let jitter = this.calculateJitter(latencies);
let testDuration = Date.now() - startTime;
let result: SpeedTestResult = {
downloadSpeed: downloadSpeed,
uploadSpeed: 0, // 暂不测上传
latency: latency,
jitter: jitter,
timestamp: Date.now(),
testDuration: testDuration
};
console.info(`测速完成: 下载速度 ${downloadSpeed.toFixed(2)} KB/s, 延迟 ${latency.toFixed(2)} ms`);
return result;
}
// 下载测试文件
private async downloadTestFile(): Promise<{ speed: number; latency: number }> {
let httpRequest = http.createHttp();
let startTime = Date.now();
let firstByteTime = number = 0;
let downloadedBytes = 0;
try {
let response = await httpRequest.request(this.config.testUrl, {
method: http.RequestMethod.GET,
connectTimeout: this.config.timeout,
readTimeout: this.config.timeout
});
// 计算时间
let endTime = Date.now();
let totalTime = endTime - startTime;
// 获取下载大小
if (response.result instanceof ArrayBuffer) {
downloadedBytes = response.result.byteLength;
} else if (typeof response.result === 'string') {
downloadedBytes = response.result.length;
}
// 计算速度(KB/s)
let speed = (downloadedBytes / 1024) / (totalTime / 1000);
// 估算延迟(TTFB - Time To First Byte)
// 简化处理,使用总时间的10%作为延迟估计
let latency = totalTime * 0.1;
return {
speed: speed,
latency: latency
};
} catch (error) {
console.error('下载测试失败:', error);
return {
speed: 0,
latency: 9999
};
} finally {
httpRequest.destroy();
}
}
// 计算平均值
private average(values: number[]): number {
if (values.length === 0) return 0;
return values.reduce((sum, v) => sum + v, 0) / values.length;
}
// 计算抖动
private calculateJitter(latencies: number[]): number {
if (latencies.length < 2) return 0;
let differences: number[] = [];
for (let i = 1; i < latencies.length; i++) {
differences.push(Math.abs(latencies[i] - latencies[i - 1]));
}
return this.average(differences);
}
// 设置进度回调
setOnProgress(callback: (progress: number, speed: number) => void): void {
this.onProgress = callback;
}
}
// 测速页面
@Entry
@Component
struct SpeedTestPage {
@State isTesting: boolean = false;
@State progress: number = 0;
@State currentSpeed: number = 0;
@State result: SpeedTestResult | null = null;
private tester: BandwidthTester = new BandwidthTester();
aboutToAppear() {
this.tester.setOnProgress((progress, speed) => {
this.progress = progress * 100;
this.currentSpeed = speed;
});
}
// 开始测速
async startTest() {
this.isTesting = true;
this.progress = 0;
this.currentSpeed = 0;
this.result = null;
try {
this.result = await this.tester.runTest();
} catch (error) {
console.error('测速失败:', error);
}
this.isTesting = false;
}
build() {
Column() {
Text('网络带宽测速')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 30 })
// 测速进度
if (this.isTesting) {
Column() {
Progress({ value: this.progress, total: 100, type: ProgressType.Linear })
.width('80%')
.height(10)
Text(`当前速度: ${this.currentSpeed.toFixed(2)} KB/s`)
.fontSize(16)
.margin({ top: 10 })
Text(`进度: ${this.progress.toFixed(0)}%`)
.fontSize(14)
.fontColor('#666')
.margin({ top: 5 })
}
.width('100%')
.padding(20)
}
// 测速结果
if (this.result && !this.isTesting) {
this.ResultCard()
}
// 开始按钮
Button(this.isTesting ? '测速中...' : '开始测速')
.width('80%')
.height(50)
.enabled(!this.isTesting)
.onClick(() => this.startTest())
.margin({ top: 30 })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
@Builder
ResultCard() {
Column() {
// 下载速度
Row() {
Text('下载速度')
.fontSize(16)
.layoutWeight(1)
Text(`${(this.result!.downloadSpeed / 1024).toFixed(2)} Mbps`)
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#4A90E2')
}
.width('100%')
// 延迟
Row() {
Text('网络延迟')
.fontSize(16)
.layoutWeight(1)
Text(`${this.result!.latency.toFixed(0)} ms`)
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor(this.getLatencyColor())
}
.width('100%')
.margin({ top: 15 })
// 抖动
Row() {
Text('网络抖动')
.fontSize(16)
.layoutWeight(1)
Text(`${this.result!.jitter.toFixed(0)} ms`)
.fontSize(20)
.fontWeight(FontWeight.Bold)
}
.width('100%')
.margin({ top: 15 })
// 质量评级
Row() {
Text('网络质量')
.fontSize(16)
.layoutWeight(1)
Text(this.getQualityLevel())
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor(this.getQualityColor())
}
.width('100%')
.margin({ top: 15 })
// 测试耗时
Text(`测试耗时: ${this.result!.testDuration} ms`)
.fontSize(12)
.fontColor('#999')
.margin({ top: 20 })
}
.width('80%')
.padding(20)
.backgroundColor('#F5F5F5')
.borderRadius(10)
}
private getLatencyColor(): string {
let latency = this.result?.latency || 0;
if (latency < 50) return '#7ED321';
if (latency < 100) return '#F5A623';
return '#D0021B';
}
private getQualityLevel(): string {
let speed = this.result?.downloadSpeed || 0;
let latency = this.result?.latency || 0;
if (speed > 10240 && latency < 50) return '优秀';
if (speed > 5120 && latency < 100) return '良好';
if (speed > 1024 && latency < 200) return '一般';
if (speed > 512 && latency < 500) return '较差';
return '很差';
}
private getQualityColor(): string {
let level = this.getQualityLevel();
switch (level) {
case '优秀': return '#7ED321';
case '良好': return '#4A90E2';
case '一般': return '#F5A623';
case '较差': return '#D0021B';
default: return '#D0021B';
}
}
}
场景二:延迟与丢包检测
通过Ping方式检测网络延迟和丢包情况。
typescript
import http from '@ohos.net.http';
// 延迟检测结果
interface LatencyTestResult {
minLatency: number; // 最小延迟
maxLatency: number; // 最大延迟
avgLatency: number; // 平均延迟
packetLoss: number; // 丢包率(0-1)
jitter: number; // 抖动
samples: number; // 采样次数
timestamp: number;
}
// 延迟检测配置
interface LatencyTestConfig {
targetUrl: string; // 目标URL
count: number; // 检测次数
interval: number; // 检测间隔(ms)
timeout: number; // 单次超时(ms)
}
// 默认配置
const DEFAULT_LATENCY_CONFIG: LatencyTestConfig = {
targetUrl: 'https://api.example.com/ping',
count: 10,
interval: 100,
timeout: 5000
};
// 延迟检测器
class LatencyTester {
private config: LatencyTestConfig;
// 回调
private onProgress: ((current: number, total: number, latency: number) => void) | null = null;
constructor(config: Partial<LatencyTestConfig> = {}) {
this.config = { ...DEFAULT_LATENCY_CONFIG, ...config };
}
// 执行检测
async runTest(): Promise<LatencyTestResult> {
console.info('开始延迟检测');
let latencies: number[] = [];
let lost = 0;
for (let i = 0; i < this.config.count; i++) {
let result = await this.ping();
if (result.success) {
latencies.push(result.latency);
this.onProgress?.(i + 1, this.config.count, result.latency);
} else {
lost++;
this.onProgress?.(i + 1, this.config.count, -1);
}
// 等待间隔
if (i < this.config.count - 1) {
await this.delay(this.config.interval);
}
}
// 计算结果
let minLatency = latencies.length > 0 ? Math.min(...latencies) : 0;
let maxLatency = latencies.length > 0 ? Math.max(...latencies) : 0;
let avgLatency = latencies.length > 0 ? this.average(latencies) : 0;
let packetLoss = lost / this.config.count;
let jitter = this.calculateJitter(latencies);
let result: LatencyTestResult = {
minLatency: minLatency,
maxLatency: maxLatency,
avgLatency: avgLatency,
packetLoss: packetLoss,
jitter: jitter,
samples: this.config.count,
timestamp: Date.now()
};
console.info(`延迟检测完成: 平均延迟 ${avgLatency.toFixed(2)} ms, 丢包率 ${(packetLoss * 100).toFixed(1)}%`);
return result;
}
// 单次Ping
private async ping(): Promise<{ success: boolean; latency: number }> {
let httpRequest = http.createHttp();
let startTime = Date.now();
try {
// 发送HEAD请求(更快)
let response = await httpRequest.request(this.config.targetUrl, {
method: http.RequestMethod.HEAD,
connectTimeout: this.config.timeout,
readTimeout: this.config.timeout
});
let endTime = Date.now();
let latency = endTime - startTime;
// 检查响应状态
if (response.responseCode >= 200 && response.responseCode < 300) {
return {
success: true,
latency: latency
};
} else {
return {
success: false,
latency: 0
};
}
} catch (error) {
return {
success: false,
latency: 0
};
} finally {
httpRequest.destroy();
}
}
// 计算平均值
private average(values: number[]): number {
if (values.length === 0) return 0;
return values.reduce((sum, v) => sum + v, 0) / values.length;
}
// 计算抖动
private calculateJitter(latencies: number[]): number {
if (latencies.length < 2) return 0;
let differences: number[] = [];
for (let i = 1; i < latencies.length; i++) {
differences.push(Math.abs(latencies[i] - latencies[i - 1]));
}
return this.average(differences);
}
// 延迟
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 设置进度回调
setOnProgress(callback: (current: number, total: number, latency: number) => void): void {
this.onProgress = callback;
}
}
// 持续网络质量监测器
class NetworkQualityMonitor {
private latencyTester: LatencyTester;
private isMonitoring: boolean = false;
private monitorTimer: number = -1;
private monitorInterval: number = 30000; // 监测间隔
// 历史数据
private history: LatencyTestResult[] = [];
private maxHistory: number = 100;
// 当前质量
private currentQuality: NetworkQuality = NetworkQuality.UNKNOWN;
// 回调
private onQualityChange: ((quality: NetworkQuality) => void) | null = null;
constructor() {
this.latencyTester = new LatencyTester();
}
// 开始监测
start(): void {
if (this.isMonitoring) return;
this.isMonitoring = true;
// 立即执行一次
this.runMonitor();
// 设置定时监测
this.monitorTimer = setInterval(() => {
this.runMonitor();
}, this.monitorInterval);
console.info('网络质量监测已启动');
}
// 停止监测
stop(): void {
if (!this.isMonitoring) return;
this.isMonitoring = false;
if (this.monitorTimer !== -1) {
clearInterval(this.monitorTimer);
this.monitorTimer = -1;
}
console.info('网络质量监测已停止');
}
// 执行监测
private async runMonitor(): Promise<void> {
try {
let result = await this.latencyTester.runTest();
// 保存历史
this.history.push(result);
if (this.history.length > this.maxHistory) {
this.history.shift();
}
// 评估质量
let quality = this.evaluateQuality(result);
// 检查质量变化
if (quality !== this.currentQuality) {
this.currentQuality = quality;
this.onQualityChange?.(quality);
console.info(`网络质量变化: ${quality}`);
}
} catch (error) {
console.error('网络质量监测失败:', error);
}
}
// 评估质量
private evaluateQuality(result: LatencyTestResult): NetworkQuality {
// 综合评估延迟和丢包
let latency = result.avgLatency;
let packetLoss = result.packetLoss;
// 丢包率优先判断
if (packetLoss > 0.3) return NetworkQuality.VERY_POOR;
if (packetLoss > 0.1) return NetworkQuality.POOR;
// 延迟判断
if (latency < 50) return NetworkQuality.EXCELLENT;
if (latency < 100) return NetworkQuality.GOOD;
if (latency < 200) return NetworkQuality.FAIR;
if (latency < 500) return NetworkQuality.POOR;
return NetworkQuality.VERY_POOR;
}
// 获取当前质量
getCurrentQuality(): NetworkQuality {
return this.currentQuality;
}
// 获取历史数据
getHistory(): LatencyTestResult[] {
return [...this.history];
}
// 获取统计摘要
getSummary(): {
avgLatency: number;
avgPacketLoss: number;
qualityTrend: 'improving' | 'stable' | 'degrading';
} {
if (this.history.length === 0) {
return {
avgLatency: 0,
avgPacketLoss: 0,
qualityTrend: 'stable'
};
}
// 计算平均延迟和丢包
let totalLatency = this.history.reduce((sum, r) => sum + r.avgLatency, 0);
let totalLoss = this.history.reduce((sum, r) => sum + r.packetLoss, 0);
let avgLatency = totalLatency / this.history.length;
let avgPacketLoss = totalLoss / this.history.length;
// 分析趋势
let qualityTrend: 'improving' | 'stable' | 'degrading' = 'stable';
if (this.history.length >= 3) {
let recent = this.history.slice(-3);
let latencies = recent.map(r => r.avgLatency);
// 简单趋势判断
if (latencies[2] < latencies[0] * 0.8) {
qualityTrend = 'improving';
} else if (latencies[2] > latencies[0] * 1.2) {
qualityTrend = 'degrading';
}
}
return {
avgLatency: avgLatency,
avgPacketLoss: avgPacketLoss,
qualityTrend: qualityTrend
};
}
// 设置回调
setOnQualityChange(callback: (quality: NetworkQuality) => void): void {
this.onQualityChange = callback;
}
}
// 网络质量枚举
enum NetworkQuality {
UNKNOWN = 'unknown',
EXCELLENT = 'excellent',
GOOD = 'good',
FAIR = 'fair',
POOR = 'poor',
VERY_POOR = 'very_poor'
}
// 延迟检测页面
@Entry
@Component
struct LatencyTestPage {
@State isTesting: boolean = false;
@State testResult: LatencyTestResult | null = null;
@State quality: NetworkQuality = NetworkQuality.UNKNOWN;
@State history: LatencyTestResult[] = [];
private tester: LatencyTester = new LatencyTester();
private monitor: NetworkQualityMonitor = new NetworkQualityMonitor();
aboutToAppear() {
this.monitor.setOnQualityChange((quality) => {
this.quality = quality;
this.history = this.monitor.getHistory();
});
this.tester.setOnProgress((current, total, latency) => {
console.info(`检测进度: ${current}/${total}, 延迟: ${latency} ms`);
});
}
aboutToDisappear() {
this.monitor.stop();
}
// 执行单次测试
async runTest() {
this.isTesting = true;
this.testResult = await this.tester.runTest();
this.isTesting = false;
}
// 开始持续监测
startMonitor() {
this.monitor.start();
}
// 停止监测
stopMonitor() {
this.monitor.stop();
}
build() {
Column() {
Text('网络延迟检测')
.fontSize(20)
.fontWeight(FontWeight.Bold)
// 当前质量
Row() {
Text('当前质量:')
.fontSize(16)
Text(this.getQualityText())
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(this.getQualityColor())
.margin({ left: 10 })
}
.margin({ top: 20 })
// 测试结果
if (this.testResult) {
Column() {
Text(`平均延迟: ${this.testResult.avgLatency.toFixed(0)} ms`)
Text(`最小延迟: ${this.testResult.minLatency.toFixed(0)} ms`)
Text(`最大延迟: ${this.testResult.maxLatency.toFixed(0)} ms`)
Text(`丢包率: ${(this.testResult.packetLoss * 100).toFixed(1)}%`)
Text(`抖动: ${this.testResult.jitter.toFixed(0)} ms`)
}
.margin({ top: 20 })
.alignItems(HorizontalAlign.Start)
}
// 操作按钮
Row() {
Button('单次测试')
.onClick(() => this.runTest())
.enabled(!this.isTesting)
Button('开始监测')
.onClick(() => this.startMonitor())
.margin({ left: 10 })
Button('停止监测')
.onClick(() => this.stopMonitor())
.margin({ left: 10 })
}
.margin({ top: 30 })
}
.width('100%')
.padding(20)
}
private getQualityText(): string {
switch (this.quality) {
case NetworkQuality.EXCELLENT: return '优秀';
case NetworkQuality.GOOD: return '良好';
case NetworkQuality.FAIR: return '一般';
case NetworkQuality.POOR: return '较差';
case NetworkQuality.VERY_POOR: return '很差';
default: return '未知';
}
}
private getQualityColor(): string {
switch (this.quality) {
case NetworkQuality.EXCELLENT: return '#7ED321';
case NetworkQuality.GOOD: return '#4A90E2';
case NetworkQuality.FAIR: return '#F5A623';
case NetworkQuality.POOR: return '#D0021B';
case NetworkQuality.VERY_POOR: return '#D0021B';
default: return '#999';
}
}
}
场景三:请求性能采样
通过记录实际请求的性能数据来评估网络质量。
typescript
import http from '@ohos.net.http';
// 请求性能样本
interface RequestSample {
url: string;
method: string;
dnsTime: number; // DNS解析时间
connectTime: number; // TCP连接时间
ttfb: number; // Time To First Byte
downloadTime: number; // 下载时间
totalTime: number; // 总时间
bytesReceived: number; // 接收字节数
success: boolean; // 是否成功
timestamp: number;
}
// 性能统计
interface PerformanceStats {
avgDnsTime: number;
avgConnectTime: number;
avgTtfb: number;
avgDownloadSpeed: number; // KB/s
successRate: number;
sampleCount: number;
timeRange: {
start: number;
end: number;
};
}
// 请求性能采样器
class RequestPerformanceSampler {
private samples: RequestSample[] = [];
private maxSamples: number = 1000;
// 记录请求性能
recordRequest(
url: string,
method: string,
timings: {
startTime: number;
dnsEnd?: number;
connectEnd?: number;
firstByteTime: number;
endTime: number;
},
bytesReceived: number,
success: boolean
): void {
let sample: RequestSample = {
url: url,
method: method,
dnsTime: timings.dnsEnd ? timings.dnsEnd - timings.startTime : 0,
connectTime: timings.connectEnd && timings.dnsEnd ?
timings.connectEnd - timings.dnsEnd : 0,
ttfb: timings.firstByteTime - timings.startTime,
downloadTime: timings.endTime - timings.firstByteTime,
totalTime: timings.endTime - timings.startTime,
bytesReceived: bytesReceived,
success: success,
timestamp: timings.startTime
};
this.samples.push(sample);
// 限制样本数量
if (this.samples.length > this.maxSamples) {
this.samples.shift();
}
}
// 计算统计
calculateStats(timeWindow?: number): PerformanceStats {
let now = Date.now();
let windowStart = timeWindow ? now - timeWindow : 0;
// 过滤时间窗口内的样本
let filteredSamples = this.samples.filter(s => s.timestamp >= windowStart);
if (filteredSamples.length === 0) {
return {
avgDnsTime: 0,
avgConnectTime: 0,
avgTtfb: 0,
avgDownloadSpeed: 0,
successRate: 0,
sampleCount: 0,
timeRange: { start: now, end: now }
};
}
// 计算平均值
let totalDns = filteredSamples.reduce((sum, s) => sum + s.dnsTime, 0);
let totalConnect = filteredSamples.reduce((sum, s) => sum + s.connectTime, 0);
let totalTtfb = filteredSamples.reduce((sum, s) => sum + s.ttfb, 0);
let successCount = filteredSamples.filter(s => s.success).length;
// 计算下载速度
let totalBytes = filteredSamples.reduce((sum, s) => sum + s.bytesReceived, 0);
let totalDownloadTime = filteredSamples.reduce((sum, s) => sum + s.downloadTime, 0);
let avgDownloadSpeed = totalDownloadTime > 0 ?
(totalBytes / 1024) / (totalDownloadTime / 1000) : 0;
let timestamps = filteredSamples.map(s => s.timestamp);
return {
avgDnsTime: totalDns / filteredSamples.length,
avgConnectTime: totalConnect / filteredSamples.length,
avgTtfb: totalTtfb / filteredSamples.length,
avgDownloadSpeed: avgDownloadSpeed,
successRate: successCount / filteredSamples.length,
sampleCount: filteredSamples.length,
timeRange: {
start: Math.min(...timestamps),
end: Math.max(...timestamps)
}
};
}
// 获取最近的样本
getRecentSamples(count: number = 10): RequestSample[] {
return this.samples.slice(-count);
}
// 清除样本
clear(): void {
this.samples = [];
}
}
// 带性能采样的HTTP客户端
class SampledHttpClient {
private sampler: RequestPerformanceSampler;
constructor(sampler: RequestPerformanceSampler) {
this.sampler = sampler;
}
// 发送请求(带性能采样)
async request(url: string, options: http.HttpRequestOptions): Promise<http.HttpResponse> {
let httpRequest = http.createHttp();
let startTime = Date.now();
let firstByteTime = number = 0;
let bytesReceived = 0;
let success = false;
try {
let response = await httpRequest.request(url, options);
firstByteTime = Date.now(); // 简化处理
// 获取响应大小
if (response.result instanceof ArrayBuffer) {
bytesReceived = response.result.byteLength;
} else if (typeof response.result === 'string') {
bytesReceived = response.result.length;
}
success = response.responseCode >= 200 && response.responseCode < 300;
let endTime = Date.now();
// 记录性能
this.sampler.recordRequest(
url,
options.method?.toString() || 'GET',
{
startTime: startTime,
firstByteTime: firstByteTime,
endTime: endTime
},
bytesReceived,
success
);
return response;
} catch (error) {
let endTime = Date.now();
// 记录失败请求
this.sampler.recordRequest(
url,
options.method?.toString() || 'GET',
{
startTime: startTime,
firstByteTime: endTime,
endTime: endTime
},
0,
false
);
throw error;
} finally {
httpRequest.destroy();
}
}
// 获取采样器
getSampler(): RequestPerformanceSampler {
return this.sampler;
}
}
// 性能监控页面
@Entry
@Component
struct PerformanceMonitorPage {
@State stats: PerformanceStats | null = null;
@State recentSamples: RequestSample[] = [];
private sampler: RequestPerformanceSampler = new RequestPerformanceSampler();
private httpClient: SampledHttpClient | null = null;
aboutToAppear() {
this.httpClient = new SampledHttpClient(this.sampler);
// 定期更新统计
setInterval(() => {
this.stats = this.sampler.calculateStats(60000); // 最近1分钟
this.recentSamples = this.sampler.getRecentSamples(10);
}, 5000);
}
// 测试请求
async testRequest() {
try {
await this.httpClient?.request('https://api.example.com/data', {
method: http.RequestMethod.GET
});
} catch (error) {
console.error('请求失败:', error);
}
}
build() {
Column() {
Text('请求性能监控')
.fontSize(20)
.fontWeight(FontWeight.Bold)
// 统计信息
if (this.stats) {
Column() {
Text(`平均DNS时间: ${this.stats.avgDnsTime.toFixed(0)} ms`)
Text(`平均连接时间: ${this.stats.avgConnectTime.toFixed(0)} ms`)
Text(`平均TTFB: ${this.stats.avgTtfb.toFixed(0)} ms`)
Text(`平均下载速度: ${this.stats.avgDownloadSpeed.toFixed(0)} KB/s`)
Text(`成功率: ${(this.stats.successRate * 100).toFixed(1)}%`)
Text(`样本数: ${this.stats.sampleCount}`)
}
.margin({ top: 20 })
.alignItems(HorizontalAlign.Start)
}
// 测试按钮
Button('发送测试请求')
.onClick(() => this.testRequest())
.margin({ top: 20 })
// 最近样本
if (this.recentSamples.length > 0) {
Column() {
Text('最近请求:')
.fontSize(14)
ForEach(this.recentSamples, (sample: RequestSample, index: number) => {
Text(`${sample.method} ${sample.url}: ${sample.totalTime}ms`)
.fontSize(12)
})
}
.margin({ top: 20 })
.alignItems(HorizontalAlign.Start)
}
}
.width('100%')
.padding(20)
}
}
四、踩坑与注意事项
坑点一:测速消耗流量
带宽测速需要下载大文件,会消耗用户流量。
typescript
// ✅ 正确:仅在WiFi下测速
async function safeSpeedTest(networkManager: NetworkStatusManager) {
if (!networkManager.isWifi()) {
console.warn('非WiFi环境,跳过测速');
return null;
}
let tester = new BandwidthTester();
return await tester.runTest();
}
坑点二:测速结果受服务器影响
测速结果受测试服务器影响,可能不准确。
typescript
// ✅ 正确:多服务器测速取最优
async function multiServerSpeedTest(): Promise<SpeedTestResult> {
let servers = [
'https://cdn1.example.com/test.bin',
'https://cdn2.example.com/test.bin',
'https://cdn3.example.com/test.bin'
];
let results: SpeedTestResult[] = [];
for (let server of servers) {
let tester = new BandwidthTester({ testUrl: server });
let result = await tester.runTest();
results.push(result);
}
// 取最快的结果
return results.reduce((best, current) =>
current.downloadSpeed > best.downloadSpeed ? current : best
);
}
坑点三:频繁检测影响性能
过于频繁的网络检测会影响应用性能。
typescript
// ✅ 正确:合理设置检测间隔
const MONITOR_CONFIG = {
// 正常情况:每分钟检测一次
normalInterval: 60000,
// 弱网情况:每30秒检测一次
weakInterval: 30000,
// 离线情况:每10秒检测一次(等待恢复)
offlineInterval: 10000
};
function getMonitorInterval(quality: NetworkQuality): number {
switch (quality) {
case NetworkQuality.EXCELLENT:
case NetworkQuality.GOOD:
return MONITOR_CONFIG.normalInterval;
case NetworkQuality.FAIR:
case NetworkQuality.POOR:
return MONITOR_CONFIG.weakInterval;
default:
return MONITOR_CONFIG.offlineInterval;
}
}
五、HarmonyOS 6适配指南
5.1 系统级网络质量API
HarmonyOS 6提供了系统级的网络质量评估API。
typescript
import connection from '@ohos.net.connection';
// HarmonyOS 6: 获取网络质量信息
async function getSystemNetworkQuality(): Promise<void> {
let netHandle = await connection.getDefaultNet();
// 获取网络能力
let capabilities = await connection.getNetCapabilities(netHandle);
// 下行带宽
let downlinkKbps = capabilities.linkDownBandwidthKbps;
console.info(`下行带宽: ${downlinkKbps} Kbps`);
// 上行带宽
let uplinkKbps = capabilities.linkUpBandwidthKbps;
console.info(`上行带宽: ${uplinkKbps} Kbps`);
// HarmonyOS 6: 获取详细质量信息
// let quality = await connection.getNetworkQuality();
// console.info(`质量评分: ${quality.score}`);
// console.info(`时延: ${quality.delay} ms`);
// console.info(`丢包率: ${quality.packetLoss}`);
}
5.2 网络质量变化监听
HarmonyOS 6支持网络质量变化事件。
typescript
// HarmonyOS 6: 监听网络质量变化
let netConnection = connection.createNetConnection();
netConnection.on('netQualityChange', (quality: connection.NetworkQuality) => {
console.info('网络质量变化');
console.info(`评分: ${quality.score}`);
console.info(`时延等级: ${quality.delayLevel}`);
console.info(`带宽等级: ${quality.bandwidthLevel}`);
// 根据质量调整应用行为
adjustAppBehavior(quality);
});
六、总结一下下
网络质量评估是精细化网络管理的关键。本文从三个方案展开:
带宽测速:通过下载测试文件测量实际带宽。注意消耗流量、多服务器测试、结果缓存等问题。
延迟检测:通过Ping方式测量延迟和丢包。开销小、可频繁执行,适合持续监测。
请求采样:记录实际请求的性能数据。真实反映用户体验,无需额外开销。
三个常见坑点:测速消耗流量、结果受服务器影响、频繁检测影响性能。遇到质量评估问题时,先排查这三个方面。
HarmonyOS 6带来了系统级网络质量API,可以直接获取带宽、延迟、丢包等信息,大大简化了实现难度。结合自定义检测方案,可以构建完善的网络质量评估体系。
至此,我们的网络基础系列文章全部完成。从HTTP请求到WebSocket长连接,从TCP/UDP Socket到网络状态监听,再到网络质量评估,希望这一系列文章能帮助你全面掌握鸿蒙网络编程!