鴻蒙學習實戰之路 - 網絡重連最佳實踐

網絡重連是確保應用在網絡波動情況下保持穩定運行的關鍵技術,合理實現可以顯著提升用户體驗

關於本文

本文基於華為官方文檔整理,結合實際開發經驗,提供 HarmonyOS 應用網絡重連的實用指南

華為開發者聯盟 - 應用網絡重連

  • 本文並不能代替官方文檔,所有內容基於官方文檔+實踐記錄
  • 所有代碼示例都有詳細註釋,建議自己動手嘗試
  • 基本所有關鍵功能都會附上對應的文檔鏈接,強烈建議你點看看看
  • 本文將通過實際案例介紹 HarmonyOS 網絡重連的實現方式和最佳實踐

代碼測試環境

確保你的開發環境符合以下要求:

軟件/工具

版本要求

HarmonyOS SDK

API Level 11+

TypeScript

5.0+

DevEco Studio

4.1+

設備要求

支持 HarmonyOS NEXT 的真機或模擬器

概述

網絡重連是指在網絡連接出現中斷或異常斷開的情況下,設備或應用程序重新建立網絡連接的過程。對於許多依賴網絡的業務和應用來説,網絡重連能夠確保在網絡出現短暫中斷後,業務能夠快速恢復,減少因網絡故障導致的業務中斷時間,提高業務的連續性和可靠性。

在 HarmonyOS 中,網絡重連主要應用於以下場景:

  1. 網絡超時重連:客户端向服務器發送請求後,如果發生網絡超時,自動嘗試重新建立連接
  2. 網絡切換重連:當網絡連接發生變化(如從 Wi-Fi 切換到移動數據)後,應用需要檢測網絡狀態變化並重新建立連接
  3. 應用前後台切換後重連:應用切換到後台一段時間後,網絡資源會被凍結釋放,需要客户端重新建立連接
  4. 遊戲場景重連:遊戲過程中因網絡異常導致掉線,支持玩家重新連接回原遊戲房間/隊伍

本文將從以下幾個方面介紹 HarmonyOS 網絡重連的最佳實踐:

  1. 網絡狀態監測與監聽
  2. 不同場景下的網絡重連實現
  3. 網絡超時與重試機制
  4. 遊戲場景下的掉線重連
  5. 網絡重連的性能與安全考慮

1. 網絡狀態監測與監聽

1.1 網絡狀態監聽基礎

在 HarmonyOS 中,可以使用 connection 模塊來監聽網絡狀態變化,這是實現網絡重連的基礎。

import connection from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';

export class NetworkMonitor {
  private netConnection: connection.NetConnection;

  constructor() {
    // 創建網絡連接實例
    this.netConnection = connection.createNetConnection();
  }

  // 註冊網絡狀態監聽
  registerNetworkListener() {
    this.netConnection.register((error: BusinessError) => {
      if (error) {
        console.error(`網絡監聽註冊失敗: ${JSON.stringify(error)}`);
        return;
      }

      // 監聽網絡可用事件
      this.netConnection.on('netAvailable', (data) => {
        console.log(`網絡已可用: ${JSON.stringify(data)}`);
        // 網絡恢復後執行重連邏輯
        this.handleNetworkReconnect();
      });

      // 監聽網絡能力變化事件
      this.netConnection.on('netCapabilitiesChange', (data: connection.NetCapabilityInfo) => {
        console.log(`網絡能力變化: ${JSON.stringify(data)}`);
        // 根據網絡能力變化調整重連策略
        this.adjustReconnectStrategy(data);
      });

      // 監聽網絡丟失事件
      this.netConnection.on('netLost', (data) => {
        console.log(`網絡已丟失: ${JSON.stringify(data)}`);
        // 網絡丟失時執行相關處理
        this.handleNetworkLost();
      });

      // 監聽網絡不可用事件
      this.netConnection.on('netUnavailable', (data) => {
        console.log(`網絡不可用: ${JSON.stringify(data)}`);
        // 網絡不可用時執行相關處理
        this.handleNetworkUnavailable();
      });
    });
  }

  // 處理網絡重連
  private handleNetworkReconnect() {
    console.log('執行網絡重連邏輯');
    // 此處添加具體的重連邏輯
  }

  // 根據網絡能力調整重連策略
  private adjustReconnectStrategy(netCapabilities: connection.NetCapabilityInfo) {
    console.log('根據網絡能力調整重連策略');
    // 此處添加根據網絡能力調整重連策略的邏輯
  }

  // 處理網絡丟失
  private handleNetworkLost() {
    console.log('處理網絡丟失情況');
    // 此處添加網絡丟失時的處理邏輯
  }

  // 處理網絡不可用
  private handleNetworkUnavailable() {
    console.log('處理網絡不可用情況');
    // 此處添加網絡不可用時的處理邏輯
  }

  // 註銷網絡狀態監聽
  unregisterNetworkListener() {
    this.netConnection.unregister();
    console.log('網絡狀態監聽已註銷');
  }
}

1.2 獲取當前網絡狀態

在實現網絡重連之前,通常需要先獲取當前的網絡狀態,以便決定是否需要執行重連操作。

import connection from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';

// 獲取當前網絡狀態
export async function getCurrentNetworkStatus(): Promise<connection.NetAvailableInfo | null> {
  try {
    const netAvailableInfo = await connection.getNetAvailable();
    return netAvailableInfo;
  } catch (error) {
    console.error(`獲取網絡狀態失敗: ${JSON.stringify(error as BusinessError)}`);
    return null;
  }
}

// 檢查網絡是否可用
export async function isNetworkAvailable(): Promise<boolean> {
  const netStatus = await getCurrentNetworkStatus();
  return netStatus?.isAvailable || false;
}

// 獲取當前網絡類型
export async function getCurrentNetworkType(): Promise<connection.NetBearType | null> {
  try {
    const netCapabilities = await connection.getNetCapabilities();
    return netCapabilities?.bearerTypes[0] || null;
  } catch (error) {
    console.error(`獲取網絡類型失敗: ${JSON.stringify(error as BusinessError)}`);
    return null;
  }
}

2. 不同場景下的網絡重連實現

2.1 網絡超時重連

場景描述

在網絡請求中,經常會遇到網絡波動、服務器宕機等情況,從而導致網絡不可用、網絡超時等問題。為了減少網絡超時等帶來的影響,在實際應用開發中經常使用超時機制和重試機制。

實現原理

網絡超時分為網絡連接超時和網絡讀取超時:

  • 連接超時:客户端嘗試與服務器建立連接但未能在規定時間內完成
  • 讀取超時:客户端已經與服務器建立連接,但未能在規定時間內獲取到響應數據

重試機制一般配合超時機制一起使用,指的是多次發送相同的請求來避免瞬態故障和偶然性故障。

代碼實現

import http from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';

// 網絡請求配置
interface RequestConfig {
  url: string;
  method?: http.RequestMethod;
  header?: Record<string, string>;
  data?: any;
  timeout?: number;
}

// 重試配置
interface RetryConfig {
  maxRetries: number;
  retryDelay: number;
}

// 帶重試機制的網絡請求
export async function requestWithRetry(
  config: RequestConfig,
  retryConfig: RetryConfig = { maxRetries: 3, retryDelay: 1000 }
): Promise<http.HttpResponse> {
  let retries = 0;
  const { maxRetries, retryDelay } = retryConfig;

  while (true) {
    try {
      console.log(`發起網絡請求 (嘗試 ${retries + 1}/${maxRetries + 1}): ${config.url}`);

      // 創建HTTP請求
      const request = http.createHttp();
      const response = await request.request(config.url, {
        method: config.method || http.RequestMethod.GET,
        header: config.header,
        extraData: config.data,
        readTimeout: config.timeout || 30000,
        connectTimeout: config.timeout || 30000
      });

      // 關閉HTTP請求
      request.destroy();

      // 檢查響應狀態
      if (response.responseCode >= 200 && response.responseCode < 300) {
        console.log(`網絡請求成功: ${config.url}`);
        return response;
      } else {
        console.warn(`網絡請求返回非成功狀態碼: ${response.responseCode}`);
        throw new Error(`HTTP Error: ${response.responseCode}`);
      }
    } catch (error) {
      retries++;
      console.error(`網絡請求失敗 (${retries}/${maxRetries + 1}): ${(error as BusinessError).message}`);

      // 檢查是否達到最大重試次數
      if (retries > maxRetries) {
        console.error(`達到最大重試次數 (${maxRetries}), 請求失敗: ${config.url}`);
        throw error;
      }

      // 等待重試延遲
      await new Promise(resolve => setTimeout(resolve, retryDelay * Math.pow(2, retries - 1)));
      console.log(`等待 ${retryDelay * Math.pow(2, retries - 1)}ms 後重試...`);
    }
  }
}

// 使用示例
async function fetchData() {
  try {
    const response = await requestWithRetry({
      url: 'https://api.example.com/data',
      method: http.RequestMethod.GET,
      timeout: 10000
    }, {
      maxRetries: 5,
      retryDelay: 500
    });

    const data = JSON.parse(response.result as string);
    console.log('獲取數據成功:', data);
    return data;
  } catch (error) {
    console.error('獲取數據失敗:', error);
    // 顯示錯誤提示
  }
}

2.2 網絡切換重連

場景描述

當設備的網絡連接發生變化時(如從 Wi-Fi 切換到移動數據,或從移動數據切換到 Wi-Fi),應用的網絡連接可能會暫時中斷,需要重新建立連接。

實現原理

通過監聽網絡狀態變化事件,當檢測到網絡類型或網絡能力發生變化時,自動觸發重連邏輯。

代碼實現

import connection from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';

class NetworkSwitchHandler {
  private netConnection: connection.NetConnection;
  private previousNetworkType: connection.NetBearType | null = null;
  private reconnectHandlers: Array<() => Promise<void>> = [];

  constructor() {
    this.netConnection = connection.createNetConnection();
    this.setupNetworkListener();
  }

  // 設置網絡監聽器
  private setupNetworkListener() {
    this.netConnection.register((error: BusinessError) => {
      if (error) {
        console.error(`網絡監聽註冊失敗: ${JSON.stringify(error)}`);
        return;
      }

      // 監聽網絡能力變化
      this.netConnection.on('netCapabilitiesChange', async (data: connection.NetCapabilityInfo) => {
        const currentNetworkType = data.bearerTypes[0];
        console.log(`網絡類型變化: ${this.previousNetworkType} -> ${currentNetworkType}`);

        // 檢查網絡類型是否發生變化
        if (this.previousNetworkType && currentNetworkType !== this.previousNetworkType) {
          console.log('網絡切換檢測到,執行重連邏輯');
          // 執行所有重連處理函數
          for (const handler of this.reconnectHandlers) {
            try {
              await handler();
            } catch (error) {
              console.error(`重連處理失敗: ${JSON.stringify(error)}`);
            }
          }
        }

        // 更新之前的網絡類型
        this.previousNetworkType = currentNetworkType;
      });
    });
  }

  // 註冊重連處理函數
  registerReconnectHandler(handler: () => Promise<void>) {
    this.reconnectHandlers.push(handler);
  }

  // 取消註冊重連處理函數
  unregisterReconnectHandler(handler: () => Promise<void>) {
    this.reconnectHandlers = this.reconnectHandlers.filter(h => h !== handler);
  }

  // 清理資源
  destroy() {
    this.netConnection.unregister();
    this.reconnectHandlers = [];
  }
}

// 使用示例
const networkSwitchHandler = new NetworkSwitchHandler();

// 註冊WebSocket重連處理
networkSwitchHandler.registerReconnectHandler(async () => {
  console.log('WebSocket 重連...');
  // 執行WebSocket重連邏輯
});

// 註冊數據同步重連處理
networkSwitchHandler.registerReconnectHandler(async () => {
  console.log('數據同步重連...');
  // 執行數據同步重連邏輯
});

2.3 應用前後台切換後重連

場景描述

當應用切換到後台一段時間後,系統可能會凍結或釋放應用的網絡資源,當應用再次回到前台時,需要重新建立網絡連接。

實現原理

通過監聽應用的前後台切換事件(onForegroundonBackground),當應用從後台切換到前台時,觸發網絡重連邏輯。

代碼實現

import { UIAbility, AbilityConstant } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

class AppLifecycleHandler {
  private isInBackground: boolean = false;
  private reconnectHandlers: Array<() => Promise<void>> = [];

  // 應用切換到前台
  onForeground() {
    this.isInBackground = false;
    console.log('應用切換到前台');
    // 執行所有重連處理函數
    this.executeReconnectHandlers();
  }

  // 應用切換到後台
  onBackground() {
    this.isInBackground = true;
    console.log('應用切換到後台');
    // 可以在此處執行資源釋放邏輯
  }

  // 執行所有重連處理函數
  private async executeReconnectHandlers() {
    for (const handler of this.reconnectHandlers) {
      try {
        await handler();
      } catch (error) {
        console.error(`重連處理失敗: ${JSON.stringify(error)}`);
      }
    }
  }

  // 註冊重連處理函數
  registerReconnectHandler(handler: () => Promise<void>) {
    this.reconnectHandlers.push(handler);
  }

  // 取消註冊重連處理函數
  unregisterReconnectHandler(handler: () => Promise<void>) {
    this.reconnectHandlers = this.reconnectHandlers.filter(h => h !== handler);
  }

  // 檢查應用是否在後台
  isAppInBackground(): boolean {
    return this.isInBackground;
  }
}

// 在Ability中使用
export default class EntryAbility extends UIAbility {
  private appLifecycleHandler: AppLifecycleHandler;

  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
    hilog.info(0x0000, 'EntryAbility', '%{public}s', 'Ability onCreate');
    this.appLifecycleHandler = new AppLifecycleHandler();
    
    // 註冊網絡重連處理
    this.appLifecycleHandler.registerReconnectHandler(async () => {
      console.log('執行網絡重連');
      // 此處添加網絡重連邏輯
    });
  }

  onForeground() {
    hilog.info(0x0000, 'EntryAbility', '%{public}s', 'Ability onForeground');
    this.appLifecycleHandler.onForeground();
  }

  onBackground() {
    hilog.info(0x0000, 'EntryAbility', '%{public}s', 'Ability onBackground');
    this.appLifecycleHandler.onBackground();
  }

  // 其他生命週期方法...
}

3. 遊戲場景下的掉線重連

3.1 遊戲重連概述

在遊戲過程中,因網絡狀況不佳、操作不當等原因,可能會導致意外掉線的情況。HarmonyOS 提供了遊戲聯機對戰服務,支持玩家在掉線後重新連接回原遊戲房間/隊伍。

3.2 遊戲重連實現

場景一:主動關閉客户端導致掉線

玩家進入房間/隊伍後,因主動關閉客户端而導致的掉線,需重新登錄遊戲並重連聯機對戰服務器。

import { Client, JoinRoomConfig } from '@kit.GameKit';

class GameReconnectManager {
  private client: Client;

  constructor(client: Client) {
    this.client = client;
  }

  // 初始化遊戲客户端
  async initGameClient() {
    try {
      const response = await this.client.Init();
      
      if (response.RtnCode === 0) {
        console.log('遊戲客户端初始化成功');
        
        // 檢查是否存在未完成的遊戲會話
        await this.checkPendingGameSession();
      } else {
        console.error('遊戲客户端初始化失敗:', response.RtnCode);
      }
    } catch (error) {
      console.error('初始化遊戲客户端時發生錯誤:', error);
    }
  }

  // 檢查是否存在未完成的遊戲會話
  private async checkPendingGameSession() {
    // 檢查是否存在已加入的房間
    const lastRoomId = this.client.GetLastRoomId();
    if (lastRoomId) {
      console.log(`檢測到未完成的房間會話: ${lastRoomId}`);
      // 嘗試重新加入房間
      await this.rejoinRoom(lastRoomId);
      return;
    }

    // 檢查是否存在已加入的隊伍
    const lastGroupId = this.client.GetLastGroupId();
    if (lastGroupId) {
      console.log(`檢測到未完成的隊伍會話: ${lastGroupId}`);
      // 嘗試重新加入隊伍
      await this.rejoinGroup(lastGroupId);
      return;
    }

    console.log('沒有檢測到未完成的遊戲會話');
  }

  // 重新加入房間
  private async rejoinRoom(roomId: string) {
    try {
      const joinRoomConfig = new JoinRoomConfig();
      joinRoomConfig.RoomId = roomId;
      
      const response = await this.client.JoinRoom(joinRoomConfig);
      
      if (response.RtnCode === 0) {
        console.log(`重新加入房間成功: ${roomId}`);
        // 處理重新加入房間後的邏輯
        this.handleRoomRejoined(response);
      } else {
        console.error(`重新加入房間失敗: ${response.RtnCode}`);
        // 處理加入失敗的情況
        this.handleRoomRejoinFailed(roomId, response.RtnCode);
      }
    } catch (error) {
      console.error(`重新加入房間時發生錯誤: ${error}`);
    }
  }

  // 重新加入隊伍
  private async rejoinGroup(groupId: string) {
    try {
      // 實現重新加入隊伍的邏輯
      console.log(`重新加入隊伍: ${groupId}`);
      // ...
    } catch (error) {
      console.error(`重新加入隊伍時發生錯誤: ${error}`);
    }
  }

  // 處理重新加入房間成功
  private handleRoomRejoined(response: any) {
    console.log('處理重新加入房間後的邏輯');
    // 恢復遊戲狀態
    // 同步遊戲數據
    // ...
  }

  // 處理重新加入房間失敗
  private handleRoomRejoinFailed(roomId: string, errorCode: number) {
    console.log(`處理重新加入房間失敗 (錯誤碼: ${errorCode})`);
    // 顯示錯誤提示
    // 引導用户創建新遊戲或加入其他房間
    // ...
  }
}

場景二:網絡異常導致掉線

網絡異常導致玩家客户端與聯機對戰服務端連接不上,在一定週期後服務器會將該玩家設置會掉線狀態。

import { Room } from '@kit.GameKit';

class NetworkDisconnectHandler {
  private room: Room;
  private reconnectAttempts: number = 0;
  private maxReconnectAttempts: number = 10;
  private reconnectInterval: number = 3000;
  private reconnectTimer: number | null = null;

  constructor(room: Room) {
    this.room = room;
    this.setupDisconnectListener();
  }

  // 設置斷線監聽器
  private setupDisconnectListener() {
    this.room.onDisconnect((playerInfo) => {
      console.log(`玩家掉線: ${JSON.stringify(playerInfo)}`);
      
      // 檢查是否是當前玩家掉線
      if (playerInfo.playerId === this.room.playerId) {
        console.log('當前玩家斷線,開始重連');
        this.startReconnectProcess();
      } else {
        console.log('其他玩家掉線,更新玩家狀態');
        // 處理其他玩家掉線的邏輯
      }
    });
  }

  // 開始重連過程
  private startReconnectProcess() {
    this.reconnectAttempts = 0;
    this.attemptReconnect();
  }

  // 嘗試重連
  private attemptReconnect() {
    if (this.reconnectAttempts >= this.maxReconnectAttempts) {
      console.error('達到最大重連嘗試次數,重連失敗');
      this.handleReconnectFailed();
      return;
    }

    this.reconnectAttempts++;
    console.log(`重連嘗試 ${this.reconnectAttempts}/${this.maxReconnectAttempts}`);

    this.room.reconnect()
      .then(() => {
        console.log('重連成功');
        this.handleReconnectSuccess();
      })
      .catch((error) => {
        console.error(`重連失敗: ${JSON.stringify(error)}`);
        
        // 檢查錯誤類型
        if (!error.code) {
          // 網絡不通,繼續重試
          console.log('網絡不通,將在3秒後重試');
          this.reconnectTimer = setTimeout(() => {
            this.attemptReconnect();
          }, this.reconnectInterval) as unknown as number;
        } else {
          // 其他錯誤,如超過允許重連時間
          console.error(`重連錯誤: ${error.code}`);
          this.handleReconnectFailed();
        }
      });
  }

  // 處理重連成功
  private handleReconnectSuccess() {
    console.log('重連成功,恢復遊戲狀態');
    // 清除重連定時器
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer);
      this.reconnectTimer = null;
    }
    // 恢復遊戲狀態
    // 同步遊戲數據
    // ...
  }

  // 處理重連失敗
  private handleReconnectFailed() {
    console.log('重連失敗,執行失敗處理邏輯');
    // 清除重連定時器
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer);
      this.reconnectTimer = null;
    }
    // 顯示重連失敗提示
    // 引導用户返回主界面或重新開始遊戲
    // ...
  }

  // 取消重連
  cancelReconnect() {
    console.log('取消重連');
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer);
      this.reconnectTimer = null;
    }
  }

  // 清理資源
  destroy() {
    this.cancelReconnect();
  }
}

4. 網絡重連的性能與安全考慮

4.1 性能優化

  1. 控制重試頻率

避免過於頻繁的重試,建議使用指數退避算法(Exponential Backoff)來逐漸增加重試間隔,減少服務器負載和網絡擁塞。

// 指數退避算法示例
const retryDelay = baseDelay * Math.pow(2, retryAttempts - 1) + Math.random() * jitter;
  1. 限制重試次數

設置合理的最大重試次數,避免無限重試導致資源浪費。

  1. 批量重連

當有多個網絡連接需要重連時,可以考慮批量處理,避免同時發起過多的網絡請求。

  1. 優先級處理

根據業務重要性設置重連優先級,確保關鍵業務先重連。

4.2 安全考慮

  1. 認證信息保護

在重連過程中,避免明文傳輸敏感信息如認證令牌、密碼等。

  1. 防止重放攻擊

為每次重連請求生成唯一標識符,防止攻擊者重放重連請求。

  1. 服務器驗證

重連時,服務器應驗證客户端身份和會話有效性,防止未授權訪問。

  1. 超時保護

為重連操作設置超時時間,避免無限等待導致應用卡死。

5. 網絡重連最佳實踐總結

5.1 設計原則

  1. 透明性:網絡重連過程應儘量對用户透明,減少用户感知
  2. 可靠性:確保重連邏輯能夠處理各種網絡異常情況
  3. 靈活性:根據不同業務場景和網絡條件調整重連策略
  4. 可測試性:重連邏輯應易於測試和調試

5.2 常見問題與解決方案

問題

解決方案

重連失敗導致應用卡死

設置重連超時和最大重試次數,避免無限等待

網絡頻繁波動導致頻繁重連

增加重連間隔,使用指數退避算法

重連過程中用户體驗差

顯示重連狀態提示,提供取消重連選項

重連後數據不一致

實現數據同步機制,確保重連後數據一致性

5.3 監控與分析

  1. 網絡狀態監控

實時監控應用的網絡狀態,收集網絡連接、斷開、切換等事件數據。

  1. 重連效果分析

統計重連成功率、平均重連時間、重連次數等指標,評估重連機制的效果。

  1. 異常情況分析

分析重連失敗的原因,如網絡類型、地理位置、時間等因素,優化重連策略。

結語

網絡重連是 HarmonyOS 應用開發中的重要技術,可以顯著提升應用在網絡不穩定情況下的用户體驗。本文介紹了幾種常見的網絡重連場景及其實現方式,包括網絡超時重連、網絡切換重連、應用前後台切換後重連以及遊戲場景下的掉線重連。

在實際應用開發中,應根據具體業務需求和場景選擇合適的重連策略,並注意性能優化和安全考慮。希望本文的內容能夠對你有所幫助,祝你在鴻蒙開發之路上越走越遠!


參考文檔

  • 華為開發者聯盟 - 應用網絡重連
  • HarmonyOS API 參考 - NetworkKit