##鸿蒙核心技术##运动开发## Remote Communication Kit(远场通信服务)
在上篇中,我们介绍了 RCP 网络库的核心功能,包括请求参数的封装、响应内容的转换以及拦截器与日志记录机制。这些功能为我们的网络库提供了坚实的基础。在本篇中,我们将继续深入探讨网络库的高级特性,包括错误处理、会话管理以及网络状态检测等,进一步提升网络库的健壮性和易用性。
四、网络库的高级特性:错误处理与异常管理
(一)自定义异常类
在网络请求中,错误处理是至关重要的。为了更好地管理错误,我们定义了一个 NetworkException
类,用于封装各种网络相关的异常。
typescript
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
,实现了统一的错误处理。
typescript
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
块中,对所有异常进行统一处理,确保错误信息的一致性。
五、网络库的高级特性:会话管理
(一)会话池的实现
为了优化网络请求的性能,我们实现了会话池机制。通过复用会话,可以减少频繁创建和销毁会话的开销。
typescript
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
配置,限制并发请求数量。 - 会话释放:在请求完成后,将会话放回会话池或关闭会话,以优化资源使用。
(二)会话管理的重要性
会话管理在网络请求中起着至关重要的作用。通过合理管理会话,可以显著提升网络请求的性能和稳定性。例如:
- 减少连接开销:通过复用会话,减少频繁建立和关闭连接的开销。
- 控制并发数量:通过限制并发请求数量,避免过多的并发请求对服务器造成压力。
- 资源回收:在请求完成后及时释放会话资源,避免资源泄漏。
六、网络库的高级特性:网络状态检测
在网络请求中,网络状态的检测是必不可少的。通过检测网络是否可用,可以提前避免因网络问题导致的请求失败。
typescript
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
}
}
核心点解析:
- 单例模式 :通过单例模式,确保
LibNetworkStatus
的实例在整个应用中唯一。 - 网络状态检测:通过调用鸿蒙提供的网络状态检测 API,判断网络是否可用。
七、总结
在本篇中,我们深入探讨了 RCP 网络库的高级特性,包括错误处理、会话管理以及网络状态检测等。通过这些特性,我们可以构建一个更加健壮、高效且易于使用的网络库。在下篇中,我们将通过实际案例,展示如何在鸿蒙运动项目中使用这个网络库,实现各种网络请求功能。敬请期待!