鸿蒙运动项目开发:封装超级好用的 RCP 网络库(中)—— 错误处理,会话管理与网络状态检测篇
##鸿蒙核心技术##运动开发## 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 } } }
核心点解析:
- 错误信息映射:通过
errorMessages
对象,将错误代码映射到具体的错误信息。 - 自定义错误信息:允许传入自定义的错误信息,以覆盖默认的错误描述。
- 错误分类:通过
isResponseError()
方法,区分是响应错误还是其他类型的错误。 - 错误信息动态更新:通过
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); } }); } }
核心点解析:
- 网络状态检测:在发起请求之前,通过
LibNetworkStatus.getInstance().isNetworkAvailable()
检测网络是否可用。 - URL 验证:通过
isValidUrl()
方法验证 URL 格式是否正确。 - 错误分类与抛出:根据不同的错误场景,抛出对应的
NetworkException
。 - 统一的错误处理:在
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; } }
核心点解析:
- 会话复用:通过
connectionPool
存储空闲的会话,避免频繁创建和销毁会话。 - 并发限制:根据
HttpConfig
中的maxConcurrentRequests
配置,限制并发请求数量。 - 会话释放:在请求完成后,将会话放回会话池或关闭会话,以优化资源使用。
(二)会话管理的重要性
会话管理在网络请求中起着至关重要的作用。通过合理管理会话,可以显著提升网络请求的性能和稳定性。例如:
- 减少连接开销:通过复用会话,减少频繁建立和关闭连接的开销。
- 控制并发数量:通过限制并发请求数量,避免过多的并发请求对服务器造成压力。
- 资源回收:在请求完成后及时释放会话资源,避免资源泄漏。
六、网络库的高级特性:网络状态检测
在网络请求中,网络状态的检测是必不可少的。通过检测网络是否可用,可以提前避免因网络问题导致的请求失败。
import connection from **********' 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 } }
核心点解析:
- 单例模式:通过单例模式,确保
LibNetworkStatus
的实例在整个应用中唯一。 - 网络状态检测:通过调用鸿蒙提供的网络状态检测 API,判断网络是否可用。
七、总结
在本篇中,我们深入探讨了 RCP 网络库的高级特性,包括错误处理、会话管理以及网络状态检测等。通过这些特性,我们可以构建一个更加健壮、高效且易于使用的网络库。在下篇中,我们将通过实际案例,展示如何在鸿蒙运动项目中使用这个网络库,实现各种网络请求功能。敬请期待!