目录

Application Network Reconnection

Overview

Network reconnection is the process of re-establishing a communication link when the network connection of a device or app is interrupted or becomes abnormal. For services and apps that rely heavily on network connectivity, network reconnection can quickly restore the connection after a temporary network interruption, significantly reducing service downtime and improving service continuity and reliability. It is particularly important in scenarios that have stringent requirements on real-time performance and stability, such as online financial transactions, telemedicine, and real-time audio and video playback. Depending on the specific scenario, network reconnection can be implemented in different ways.

Reconnection upon Network Timeout

[h2]Description

Network requests usually encounter issues such as network fluctuations and server downtime, leading to network unavailability and timeout problems. To minimize the impact of network timeouts and similar issues, timeout and retry mechanisms are commonly used in practical application development. For example, HTTP connection timeout and request retry settings are usually configured when an application initiates HTTP requests for list data.

[h2]How to Implement

Network timeouts are classified into network connection timeouts and network read timeouts.

Common network retry policies include scheduled retry, exponential backoff retry, and random backoff retry.

When setting the retry policy, it is crucial to consider the network timeout duration, number of retries, and retry interval to avoid network resource waste.

[h2]Development Procedure

In HarmonyOS, network reconnection is applicable when an HTTP or RCP error or timeout occurs. To implement HTTP reconnection upon timeout, perform the following steps:

Sample code:

async getHttpRequest(url: string, retry: number): Promise<number | undefined> {
  try {
    return await this.httpRequest?.requestInStream(url,
      { method: http.RequestMethod.GET, connectTimeout: 6000, readTimeout: 60000 })
      .then((data: number) => {
        if ((data === 408 || data === 500) && retry > 0) {
          return new Promise((resolve: Function) => {
            setTimeout(() => {
              resolve(this.getHttpRequest(url, retry - 1));
            }, 2000);
          });
        } else {
          return data;
        }
      });
  } catch (err) {
    this.isDownload = false;
    try {
      this.getUIContext().getPromptAction().showToast({ message: $r('app.string.download_error') });
    } catch (error) {
      let err = error as BusinessError;
      hilog.error(0xFF00, 'NetworkReconnection', `showToast fail, code = ${err.code}, message = ${err.message}`);
    }
    return;
  }
}

The implementation of the RCP reconnection upon timeout is similar to that of the HTTP reconnection. The sample code for the RCP reconnection upon timeout is as follows:

createRCPSession(): rcp.Session | null {
  try {
    const customHttpEventsHandler: rcp.HttpEventsHandler = {
      onDownloadProgress: (totalSize: number, transferredSize: number) => {
        this.contentLength = totalSize;
        this.downloadSize = transferredSize;
        this.process = this.contentLength === 0 ? 0 : Math.floor(this.downloadSize / this.contentLength * 100);
      },
      onDataEnd: () => {
        this.contentLength = -1;
        this.downloadSize = 0;
      },
    };
    const sessionConfig: rcp.SessionConfiguration = {
      requestConfiguration: {
        transfer: {
          timeout: {
            connectMs: 6000,
            transferMs: 60000
          }
        },
        tracing: { httpEventsHandler: customHttpEventsHandler }
      }
    }
    return rcp.createSession(sessionConfig);
  } catch (error) {
    return null;
  }
}

// ...

async getRcpRequest(url: string, retry: number): Promise<rcp.Response | undefined> {
  try {
    if (this.session !== null) {
      return await this.session.get(url)
        .then((response) => {
          if ((response.statusCode === 408 || response.statusCode === 500) && retry > 0) {
            return new Promise((resolve: Function) => {
              setTimeout(() => {
                resolve(this.getRcpRequest(url, retry - 1));
              }, 2000)
            })
          } else {
            return response;
          }
        })
    } else {
      return;
    }
  } catch (err) {
    try {
      this.getUIContext().getPromptAction().showToast({ message: $r('app.string.download_error') });
      this.isDownload = false;
    } catch (error) {
      let err = error as BusinessError;
      Logger.error('NetworkReconnection', `showToast fail, code = ${err.code}, message = ${err.message}`);
    }
    return;
  }
}

Reconnection upon Network Switching

[h2]Description

In the digital age, most applications rely heavily on network connectivity, which has deeply penetrated various application scenarios. However, maintaining network connection after network state changes remains a challenge for many applications.

[h2]How to Implement

Network switching primarily involves network type switching and network availability switching. HarmonyOS provides the network connection management capability to query network information and listen to Internet connection changes.

The implementation involves following key steps:

[h2]Development Procedure

Call the register() method of connection to subscribe to network change events as well as netCapabilitiesChange events and netLost events. Trigger a netLost event when the device changes from the network connection state to the network disconnection state, and trigger a netCapabilitiesChange event vice versa. In the case of network type change, trigger both netLost and netCapabilitiesChange events. You can store the network type and network status in AppStorage within the netCapabilitiesChange event based on specific requirements.

private netCon: connection.NetConnection = connection.createNetConnection();

register() {
  this.netCon.register((error: BusinessError) => {
    Logger.error('net register' + JSON.stringify(error));
  });
}

netCapabilitiesChange() {
  this.netCon.on('netCapabilitiesChange', (data: connection.NetCapabilityInfo) => {
    let netAvailable = false;
    data.netCap.networkCap?.forEach((value) => {
      if (value === connection.NetCap.NET_CAPABILITY_INTERNET) {
        netAvailable = true;
      }
    })
    Logger.info('ConnectionUtil.netAvailable:' + netAvailable);
    AppStorage.setOrCreate('netAvailable', netAvailable);
  })

  this.netCon.on('netLost', (data: connection.NetHandle) => {
    AppStorage.setOrCreate('netAvailable', false);
    Logger.info("WifiChangeListen-- Succeeded to get data: " + JSON.stringify(data));
  });
}

Use AppStorage to store the global network status of applications.

@StorageProp('netAvailable') @Watch('onSocketUpdated') netAvailable: boolean = true;

Use @Watch to monitor the changes of status variables and initiate network reconnection based on the specific change and application scenario.

onSocketUpdated() {
  this.netAvailable ? this.tcpSocketConnect() : this.tcpSocketDisconnect();
  Logger.info('netAvailable:' + this.netAvailable);
}

// ...

tcpSocketConnect() {
  if (!this.netAvailable) {
    try {
      this.getUIContext().getPromptAction().showToast({ message: $r('app.string.connect_error') });
    } catch (error) {
      let err = error as BusinessError;
      Logger.error('NetworkReconnection', `showToast fail, code = ${err.code}, message = ${err.message}`);
    }
    return;
  }

  this.tcp = socket.constructTCPSocketInstance();
  this.tcp.on('connect', () => {
    Logger.info('on connect');
  });
  this.tcp.on('close', () => {
    Logger.info('on close');
  });
  this.tcp.on('error', (error: BusinessError) => {
    Logger.error('error:' + error.code + error.message
    );
  });
  const clientIpAddress: socket.NetAddress = {
    address: CommonConstants.IP_ADDRESS,
    port: CommonConstants.CLIENT_IP_PORT
  } as socket.NetAddress;

  this.tcp.bind(clientIpAddress, (err: BusinessError) => {
    if (err) {
      Logger.error('bind fail' + JSON.stringify(err));
      this.getUIContext().getPromptAction().showToast({ message: $r('app.string.connect_error') });
      return;
    }

    let tcpConnect: socket.TCPConnectOptions = {} as socket.TCPConnectOptions;
    const serverIpAddress: socket.NetAddress = {
      address: CommonConstants.IP_ADDRESS,
      port: CommonConstants.IP_PORT
    } as socket.NetAddress;
    tcpConnect.address = serverIpAddress;
    tcpConnect.timeout = 3000;
    this.tcp?.connect(tcpConnect, (err: BusinessError) => {
      if (err) {
        Logger.error('connect fail');
        this.getUIContext().getPromptAction().showToast({ message: $r('app.string.connect_error') });
        return;
      }
    });
  });
}

Reconnection upon Foreground-Background Switching

[h2]Description

When an application that uses TCPSocket or UDPSocket communication does not apply for a continuous task or transient task, it may encounter network unavailability or network resource abnormalities after switching to the background for a period of time. If the application continues to use the previous TCPSocket and UDPSocket objects to communicate with the server after switching back to the foreground, it may also encounter a network exception.

[h2]How to Implement

In HarmonyOS, network resources of an application will be frozen 2 seconds after the application switches to the background and will be released 12 seconds later. In this case, continuing to use network resources will result in network unavailability. If your application needs to use network resources in the background, you can use a transient task or a continuous task.

Socket communication is based on IP addresses and ports. After the application switches to the background, the IP addresses and ports of the TCP and UDP connection objects are cleared when network resources are frozen, but the connection objects are not released. After the application switches back to the foreground, the system reallocates a new IP address and port to the TCP and UDP connection objects. If the application continues to use the previous connection objects to communicate with the server, the server considers that the IP addresses and ports of the same connection object are inconsistent, leading to untrusted communication and network exceptions.

The network reconnection after application foreground/background switching has two key parts:

[h2]Development Procedure

During foreground-background switchover of an application, the foreground/background state is stored in the UIAbility lifecycle.

export default class EntryAbility extends UIAbility {
  // ...

  onForeground(): void {
    // ...
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
  }

  onBackground(): void {
    // ...
    AppStorage.setOrCreate('onForeground', false);
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
  }
}

Use @StorageProp to synchronize the foreground/background state and use @Watch to monitor state changes.

@StorageProp('onForeground') @Watch('onForegroundChange') onForeground: boolean = true;
// ...

onForegroundChange(): void {
  this.onForeground ? this.tcpSocketConnect() : this.tcpSocketDisconnect();
}

Sample Code