写点什么

鸿蒙运动项目开发:封装超级好用的 RCP 网络库(中)—— 错误处理,会话管理与网络状态检测篇

  • 2025-06-18
    北京
  • 本文字数:8502 字

    阅读完需:约 28 分钟

##鸿蒙核心技术 ##运动开发 ## Remote Communication Kit(远场通信服务)


在上篇中,我们介绍了 RCP 网络库的核心功能,包括请求参数的封装、响应内容的转换以及拦截器与日志记录机制。这些功能为我们的网络库提供了坚实的基础。在本篇中,我们将继续深入探讨网络库的高级特性,包括错误处理、会话管理以及网络状态检测等,进一步提升网络库的健壮性和易用性。

四、网络库的高级特性:错误处理与异常管理

(一)自定义异常类

在网络请求中,错误处理是至关重要的。为了更好地管理错误,我们定义了一个 NetworkException 类,用于封装各种网络相关的异常。


import { ErrorCodes } from "../NetConstants";import { BusinessError } from "@kit.BasicServicesKit";import { appLogger } from "../../../app/Application";

export class NetworkException extends Error {
private static errorMessages: Record<string, string> = { [ErrorCodes.NETWORK_UNAVAILABLE]: "网络不可用,请检查网络连接", [ErrorCodes.REQUEST_TIMEOUT]: "请求超时,请稍后重试", [ErrorCodes.SERVER_ERROR]: "服务器开小差了,请稍后再试", [ErrorCodes.INVALID_RESPONSE]: "服务器返回数据格式错误", [ErrorCodes.UNAUTHORIZED]: "登录已过期,请重新登录", [ErrorCodes.FORBIDDEN]: "无权访问该资源", [ErrorCodes.NOT_FOUND]: "请求的资源不存在", [ErrorCodes.UNKNOWN_ERROR]: "未知错误,请联系客服", [ErrorCodes.URL_NOT_EXIST_ERROR]: "URL不存在", [ErrorCodes.URL_ERROR]: "URL格式不合法", [ErrorCodes.BAD_REQUEST]: "客户端请求的语法错误,服务器无法理解。", [ErrorCodes.REQUEST_CANCEL]: "请求被取消" };
private responseCode?:number private originalError?:Error | BusinessError private _code: string;
public get code(): string { return this._code; }


constructor(code : string, originalError?: Error | BusinessError,customMessage?: string,responseCode?:number) { super(customMessage || NetworkException.getMessage(code)) this._code = code this.name = "NetworkException"; this.responseCode = responseCode this.originalError = originalError }

public isResponseError(): boolean{ if (this.responseCode) { return true }else { return false } }
private static getMessage(code: string): string { return NetworkException.errorMessages[code] || NetworkException.errorMessages[ErrorCodes.UNKNOWN_ERROR]; }

static updateErrorMessages(newMessages: Record<string, string>): void { const keys = Object.keys(newMessages); for (let i = 0; i < keys.length; i++) { const key = keys[i]; const value = newMessages[key]; NetworkException.errorMessages[key] = value } }
}
复制代码


核心点解析


  1. 错误信息映射:通过 errorMessages 对象,将错误代码映射到具体的错误信息。

  2. 自定义错误信息:允许传入自定义的错误信息,以覆盖默认的错误描述。

  3. 错误分类:通过 isResponseError() 方法,区分是响应错误还是其他类型的错误。

  4. 错误信息动态更新:通过 updateErrorMessages() 方法,允许动态更新错误信息映射表。

(二)错误处理逻辑

在网络请求中,错误处理逻辑需要覆盖多种场景,包括网络不可用、请求超时、服务器错误等。我们通过在 RcpNetworkService 类中捕获和抛出 NetworkException,实现了统一的错误处理。



async request<T>(requestOption: RequestOptions,requestKeyFun?:(str:string)=>void): Promise<T> { const session = this.rcpSessionManager.getSession(requestOption.connectTimeout??this.httpConfig.connectTimeout,requestOption.transferTimeout??this.httpConfig.transferTimeout)
try {
let baseUrl = requestOption.baseUrl?requestOption.baseUrl:this.baseUrl if(baseUrl === null || baseUrl.trim().length === 0){ throw new NetworkException(ErrorCodes.URL_NOT_EXIST_ERROR); }
if (!LibNetworkStatus.getInstance().isNetworkAvailable()) { appLogger.error("HttpCore 网络不可用") throw new NetworkException(ErrorCodes.NETWORK_UNAVAILABLE); }

let url = baseUrl + requestOption.act;
if (!isValidUrl(url)) { appLogger.error("HttpCore url格式不合法") throw new NetworkException(ErrorCodes.URL_ERROR); } const contentType = requestOption.contentType??RcpContentType.JSON
const headers: rcp.RequestHeaders = { 'Content-Type': contentType }; const cacheKey = await USystem.getUniqueId()
if (this.queryParamAppender) { let param = this.queryParamAppender.append(requestOption.queryParams); if(param){ url = url + "?" + param } }

const requestObj = new rcp.Request(url, requestOption.method??RequestMethod.GET, headers, this.converterManger.selectRequestConverter(requestOption.content,contentType)); // 将请求和会话的映射关系存储起来 this.requestMap.set(cacheKey, { session, request: requestObj }); if(requestKeyFun){ requestKeyFun(cacheKey) } let response = await session.fetch(requestObj);
if (!response.statusCode) { throw new NetworkException(ErrorCodes.INVALID_RESPONSE); }
if (response.statusCode >= HttpStatus.SUCCESS && response.statusCode < 300) { // 获取 Content-Type const responseContentType = response.headers['Content-Type']; const responseData = this.converterManger.selectResponseConverter(response, responseContentType) const parsedResult = responseData as T
return parsedResult;
}
switch (response.statusCode) { case HttpStatus.UNAUTHORIZED: throw new NetworkException(ErrorCodes.UNAUTHORIZED,undefined,undefined,response.statusCode); case HttpStatus.FORBIDDEN: throw new NetworkException(ErrorCodes.FORBIDDEN,undefined,undefined,response.statusCode); case HttpStatus.NOT_FOUND: throw new NetworkException(ErrorCodes.NOT_FOUND,undefined,undefined,response.statusCode); case HttpStatus.REQUEST_TIMEOUT: throw new NetworkException(ErrorCodes.REQUEST_TIMEOUT,undefined,undefined,response.statusCode); case HttpStatus.BAD_REQUEST: throw new NetworkException(ErrorCodes.BAD_REQUEST,undefined,undefined,response.statusCode); case HttpStatus.SERVER_ERROR: case HttpStatus.BAD_GATEWAY: case HttpStatus.SERVICE_UNAVAILABLE: case HttpStatus.GATEWAY_TIMEOUT: throw new NetworkException(ErrorCodes.SERVER_ERROR,undefined,undefined,response.statusCode); default: throw new NetworkException(ErrorCodes.UNKNOWN_ERROR,undefined,undefined,response.statusCode); }


}catch (e) { if(e instanceof NetworkException){ throw e } else { try{ let err = e as BusinessError; appLogger.error(` ${err.code.toString()} ${err.stack} ${err.message} ${err.name}`) throw new NetworkException(err.code.toString(),err,err.message) }catch { let err = e as Error; appLogger.error(`异常: ${err.stack} ${err.message} ${err.name}`) throw err } } }finally { this.rcpSessionManager?.releaseSession(session) // 当会话被关闭时,移除与该会话关联的所有请求 this.requestMap.forEach((entry, key) => { if (entry.session === session) { this.requestMap.delete(key); } }); }}
复制代码


核心点解析


  1. 网络状态检测:在发起请求之前,通过 LibNetworkStatus.getInstance().isNetworkAvailable() 检测网络是否可用。

  2. URL 验证:通过 isValidUrl() 方法验证 URL 格式是否正确。

  3. 错误分类与抛出:根据不同的错误场景,抛出对应的 NetworkException

  4. 统一的错误处理:在 catch 块中,对所有异常进行统一处理,确保错误信息的一致性。

五、网络库的高级特性:会话管理

(一)会话池的实现

为了优化网络请求的性能,我们实现了会话池机制。通过复用会话,可以减少频繁创建和销毁会话的开销。


import { rcp } from "@kit.RemoteCommunicationKit";import { HttpConfig } from "./HttpConfig";

// 创建安全配置,跳过证书验证const securityConfig: rcp.SecurityConfiguration = { remoteValidation: 'skip'};

export class RcpSessionManager{
private currentConcurrentRequests = 0;
// 定义连接池 private connectionPool: rcp.Session[] = []; private _interceptor: rcp.Interceptor[] = [];
public set interceptor(value: rcp.Interceptor[]) { this._interceptor = value; }
private _httpConfig: HttpConfig = new HttpConfig();
public set httpConfig(value: HttpConfig) { this._httpConfig = value; }
public getSession(connectTimeout:number,transferTimeout:number): rcp.Session { // 如果连接池中有可用的会话,直接返回 if (this.connectionPool.length > 0) { return this.connectionPool.pop()!; }
// 如果没有可用的会话,创建一个新的会话 const session = rcp.createSession({ interceptors: [...this._interceptor], requestConfiguration: { transfer: { timeout: { connectMs: connectTimeout, transferMs: transferTimeout } }, security: this._httpConfig.security?undefined:securityConfig } });
return session; }
public releaseSession(session: rcp.Session): void { // 如果当前并发请求小于最大并发限制,将会话放回连接池 if (this.currentConcurrentRequests < this._httpConfig.maxConcurrentRequests) { this.connectionPool.push(session); } else { session.close(); } }
public destroy(): void { // 关闭所有会话 this.connectionPool.forEach(session => session.close()); this.connectionPool.length = 0; }}
复制代码


核心点解析


  1. 会话复用:通过 connectionPool 存储空闲的会话,避免频繁创建和销毁会话。

  2. 并发限制:根据 HttpConfig 中的 maxConcurrentRequests 配置,限制并发请求数量。

  3. 会话释放:在请求完成后,将会话放回会话池或关闭会话,以优化资源使用。

(二)会话管理的重要性

会话管理在网络请求中起着至关重要的作用。通过合理管理会话,可以显著提升网络请求的性能和稳定性。例如:


  • 减少连接开销:通过复用会话,减少频繁建立和关闭连接的开销。

  • 控制并发数量:通过限制并发请求数量,避免过多的并发请求对服务器造成压力。

  • 资源回收:在请求完成后及时释放会话资源,避免资源泄漏。

六、网络库的高级特性:网络状态检测

在网络请求中,网络状态的检测是必不可少的。通过检测网络是否可用,可以提前避免因网络问题导致的请求失败。



import connection from '@ohos.net.connection'import { appLogger } from '../../app/Application'import { LibNetworkStatusCallback } from './LibNetworkStatusCallback'
const TAG : string = "LibNetworkStatus"
/** * 枚举:网络类型 */export enum NetworkType { STATE_NULL = 'NULL',//网络状态标识:未联网 UNKNOWN = 'UNKNOWN',//未知网络 MOBILE = 'MOBILE', WIFI = 'WIFI', ETHERNET = 'ETHERNET'}
/** * 枚举:承载类型(内部使用,与具体平台API对接) * 注意:这里的枚举值应与平台API中的实际值保持一致 */enum BearerType { MOBILE = 0, WIFI = 1, // ... 可能还有其他承载类型,根据平台API添加 ETHERNET = 3}
/** * 网络信息: * 1、网络连接状态管理 * 2、网络事件注册监听、取消注册 */export class LibNetworkStatus {

/** * LibNetworkStatus单例对象 */ private static instance: LibNetworkStatus /** * 当前网络状态 */ private currentNetworkStatus:NetworkType = NetworkType.STATE_NULL /** * 网络是否可用 */ private isAvailable = false /** * 鸿蒙网络连接对象 */ private networkConnectio?: connection.NetConnection /** * 定义回调方法集合,使用WeakSet避免内存泄漏 */ private callbacks = new Set<LibNetworkStatusCallback>()
/** * 防抖定时器 */ private debounceTimer: number | null = null
/** * 防抖时间(毫秒) */ private static readonly DEBOUNCE_DELAY = 300
/** * 获得LibNetworkStatus单例对象 * @returns LibNetworkStatus单例对象 */ static getInstance (): LibNetworkStatus { if (!LibNetworkStatus.instance) { LibNetworkStatus.instance = new LibNetworkStatus() } return LibNetworkStatus.instance }
/** * 添加回调方法 * @param callback 回调方法 * @param isCallBackCurrentNetworkStatus 是否立即返回当前的网络状态 */ addCallback (callback: LibNetworkStatusCallback, isCallBackCurrentNetworkStatus: boolean) { if (callback && this.callbacks) { appLogger.debug(TAG+"添加回调方法") if(this.callbacks.has(callback)){ return } this.callbacks.add(callback)
//立即回调当前网络状态 if (isCallBackCurrentNetworkStatus) { appLogger.debug(TAG+'立即回调当前网络状态: ' + this.currentNetworkStatus) callback(this.currentNetworkStatus) } } }
/** * 移除回调方法 * @param callback 回调方法 */ removeCallback (callback: LibNetworkStatusCallback) { if (callback && this.callbacks && this.callbacks.has(callback)) { appLogger.debug(TAG+'移除回调方法') this.callbacks.delete(callback) } }
/** * 防抖处理网络状态回调 */ private debouncedCallback() { if (this.debounceTimer !== null) { clearTimeout(this.debounceTimer); }
this.debounceTimer = setTimeout(() => { if (this.callbacks && this.callbacks.size > 0) { appLogger.debug(TAG + '遍历callback集合,回调当前网络状态') this.callbacks.forEach(callback => { callback(this.currentNetworkStatus) }) } this.debounceTimer = null; }, LibNetworkStatus.DEBOUNCE_DELAY); }
/** * 回调当前网络状态 */ callbackNetworkStatus() { this.debouncedCallback(); }
/** * 注册网络状态监听: * 设备从无网络到有网络会触发"netAvailable"、"netCapabilitiesChange"、"netConnectionPropertiesChange"事件; * 设备从有网络到无网络会触发"netLost"事件 * 设备从wifi到蜂窝网络会触发"netLost"事件(wifi不可用)、之后触发"netAvailable"事件(蜂窝可用) */ registerNetConnectListener () { if (this.networkConnectio) { appLogger.debug(TAG+'已订阅网络事件,无需再次订阅') return }
//创建NetConnection对象 this.networkConnectio = connection.createNetConnection()
//判断默认网络状态 let hasDefaultNet = connection.hasDefaultNetSync() if (hasDefaultNet) { appLogger.debug(TAG+'hasDefaultNetSync ' + hasDefaultNet) this.isAvailable = true //获得默认网络类型 this.getDefaultNetSync() }
//注册 this.networkConnectio.register((error) => { if (error) { appLogger.debug(TAG+'networkConnectio.register failure: ' + JSON.stringify(error)) } else { appLogger.debug(TAG+' networkConnectio.register success') } })
//订阅网络可用事件 appLogger.debug(TAG+'订阅网络可用事件-->') this.networkConnectio.on('netAvailable', (data: connection.NetHandle) => { appLogger.debug(TAG+'netAvailable:' + JSON.stringify(data)) this.isAvailable = true
//获得默认网络类型 this.getDefaultNetSync()
//回调网络状态 this.callbackNetworkStatus() })
//订阅网络丢失事件 appLogger.debug(TAG+'订阅网络丢失事件-->') this.networkConnectio.on('netLost', (data: connection.NetHandle) => { appLogger.debug(TAG+'netLost:' + JSON.stringify(data)) this.isAvailable = false this.currentNetworkStatus = NetworkType.STATE_NULL
//回调网络状态 this.callbackNetworkStatus() })
//订阅网络不可用事件 appLogger.debug(TAG+'订阅网络不可用事件-->') this.networkConnectio.on('netUnavailable', () => { appLogger.debug(TAG+'netUnavailable') this.isAvailable = false this.currentNetworkStatus = NetworkType.STATE_NULL
//回调网络状态 this.callbackNetworkStatus() }) }
/** * 获得默认网络类型 */ getDefaultNetSync () { //获得当前网络状态 let netHandle = connection.getDefaultNetSync() if (netHandle) { let capabilities = connection.getNetCapabilitiesSync(netHandle) appLogger.debug(TAG+'getNetCapabilitiesSync:' + JSON.stringify(capabilities)) if (capabilities && capabilities.bearerTypes && capabilities.bearerTypes.length > 0) {
// 获取第一个承载类型 const bearerType = capabilities.bearerTypes[0]; // 根据承载类型判断网络类型 switch (bearerType) { case BearerType.MOBILE.valueOf(): // 蜂窝网络 appLogger.debug(TAG+'currentNetworkState:蜂窝网络') this.currentNetworkStatus = NetworkType.MOBILE; break; case BearerType.WIFI.valueOf(): // Wi-Fi网络 appLogger.debug(TAG+'currentNetworkState:WIFI网络') this.currentNetworkStatus = NetworkType.WIFI; break; case BearerType.ETHERNET.valueOf(): // 以太网网络(通常移动设备不支持,但为完整性保留) appLogger.debug(TAG+'currentNetworkState:以太网网络') this.currentNetworkStatus = NetworkType.ETHERNET; break; default: // 未知网络类型 appLogger.debug(TAG+'currentNetworkState:未知网络类型') this.currentNetworkStatus = NetworkType.UNKNOWN; break; }
} } }
/** * 当前网络是否可用 */ isNetworkAvailable () { return this.isAvailable }
/** * 获得当前网络状态 * @returns */ getCurrentNetworkStatus () { return this.currentNetworkStatus }}
复制代码


核心点解析


  1. 单例模式:通过单例模式,确保 LibNetworkStatus 的实例在整个应用中唯一。

  2. 网络状态检测:通过调用鸿蒙提供的网络状态检测 API,判断网络是否可用。

七、总结

在本篇中,我们深入探讨了 RCP 网络库的高级特性,包括错误处理、会话管理以及网络状态检测等。通过这些特性,我们可以构建一个更加健壮、高效且易于使用的网络库。在下篇中,我们将通过实际案例,展示如何在鸿蒙运动项目中使用这个网络库,实现各种网络请求功能。敬请期待!

发布于: 刚刚阅读数: 2
用户头像

还未添加个人签名 2021-11-19 加入

还未添加个人简介

评论

发布
暂无评论
鸿蒙运动项目开发:封装超级好用的 RCP 网络库(中)—— 错误处理,会话管理与网络状态检测篇_王二蛋和他的张大花_InfoQ写作社区