##鸿蒙核心技术##运动开发## Remote Communication Kit(远场通信服务)
在之前的文章中,我们详细介绍了如何封装一个功能完备的 RCP 网络库,并探讨了其核心功能和高级特性。在本篇中,我们将展示如何在鸿蒙运动项目中使用这个网络库来实现具体的网络请求功能。
前言
在鸿蒙运动项目中,网络请求是实现功能的关键环节之一。无论是获取运动数据、同步用户信息,还是加载运动视频资源,都需要一个稳定、高效且易于使用的网络库。在本篇中,我们将通过实际代码样例,展示如何使用封装好的 RCP 网络库实现这些功能。
一、封装异常处理
在实际开发中,异常处理是网络请求中不可或缺的一部分。通过自定义异常类,我们可以更好地管理网络请求中可能出现的各种错误。
(一)自定义异常类
定义了一个 ApiException
类,用于封装 API 请求中的错误信息。
typescript
export class ApiException extends Error {
apiCode?: number;
apiMessage?: string;
constructor(apiCode?: number, apiMessage?: string) {
super();
this.name = "ApiException";
this.apiCode = apiCode;
this.apiMessage = apiMessage;
}
}
核心点解析:
- 自定义属性 :
apiCode
和apiMessage
用于存储 API 返回的错误代码和错误信息。 - 构造函数:通过构造函数初始化异常对象,并设置错误代码和错误信息。
二、封装网络请求
为了简化网络请求的实现,你封装了一个 ApiRequest
类,用于统一管理网络请求和错误处理。
(一)单例模式
ApiRequest
类使用了单例模式,确保全局只有一个实例。
typescript
import { DataResult, ErrorCodes, ErrorData, LibToast, NetworkException, SuccessData } from "lib_base";
import { RcpNetworkService } from "lib_base/src/main/ets/utils/rcpnet/RcpService";
import { ApiResult } from "../data/models/ApiResult";
import { ApiException } from "./ApiException";
export interface IApiRequestHandleBusinessError{
handleBusinessError(apiException: ApiException): boolean
}
export class ApiRequest{
private static instance: ApiRequest
static getInstance (): ApiRequest {
if (!ApiRequest.instance) {
ApiRequest.instance = new ApiRequest()
}
return ApiRequest.instance
}
private _net?: RcpNetworkService | undefined;
private _i_api_equest_handle_business_error?: IApiRequestHandleBusinessError | undefined;
public set i_api_equest_handle_business_error(value: IApiRequestHandleBusinessError | undefined) {
this._i_api_equest_handle_business_error = value;
}
public set net(value: RcpNetworkService | undefined) {
this._net = value;
}
public getService() : RcpNetworkService{
return this._net!;
}
}
核心点解析:
- 单例模式 :通过
getInstance
方法确保ApiRequest
的全局唯一性。 - 依赖注入 :通过
set
方法注入RcpNetworkService
和IApiRequestHandleBusinessError
,增强类的灵活性。
(二)统一请求方法
ApiRequest
类提供了一个统一的请求方法 remoteApi
,用于处理网络请求并封装返回结果。
typescript
export interface IApiRequestHandleBusinessError {
handleBusinessError(apiException: ApiException): boolean;
}
export class ApiRequest {
public static async remoteApi<T>(api:()=>Promise<ApiResult<T>>): Promise<DataResult<ApiResult<T>|null>>{
try{
const data = await api()
if(data.success && data.success){
let bean = new SuccessData<ApiResult<T>>(data)
return bean;
} else {
let bean = ErrorData.fromError(new ApiException(data.code,data.msg) as Error,data.code?.toString(),data.msg)
return bean;
}
}catch (e) {
if(e instanceof NetworkException){
let bean = ErrorData.fromError(e)
//请求被框架取消,客户端不进入异常处理,避免业务弹窗
if(e.code !== ErrorCodes.REQUEST_CANCEL){
LibToast.show(e.message)
}
return bean;
}
throw e as Error
}
}
}
核心点解析:
- 请求执行 :通过传入的
api
函数执行具体的网络请求。 - 成功处理 :如果请求成功,返回
SuccessData
对象。 - 失败处理 :如果请求失败,根据错误类型返回
ErrorData
对象。 - 网络异常处理 :捕获
NetworkException
,并根据错误代码决定是否显示提示信息。
(三)业务逻辑处理
ApiRequest
类还提供了一个 service
方法,用于处理业务逻辑和错误。
typescript
export class ApiRequest {
// ... 其他代码 ...
public static async service<T>(source:()=>Promise<DataResult<ApiResult<T>|null>>,
request : (data:DataResult<T|null>)=>void,
apiError : (apiError:ErrorData)=>void,
netError? : (netError:ErrorData)=>void,
){
let data = await source();
if(data instanceof SuccessData){
request(data.data)
}else {
let error = data as ErrorData
if(error.error&&error.error instanceof ApiException){
//业务异常
if(ApiRequest.instance._i_api_equest_handle_business_error){
ApiRequest.instance._i_api_equest_handle_business_error.handleBusinessError(error.error)
}
apiError(error)
}else {
//网络异常
if(netError){
netError(error)
}
}
}
}
}
核心点解析:
- 请求执行 :通过
source
函数执行网络请求。 - 成功处理 :如果请求成功,调用
request
回调函数处理数据。 - 错误处理 :根据错误类型调用
apiError
或netError
回调函数。 - 业务逻辑分离 :通过
IApiRequestHandleBusinessError
接口处理业务逻辑错误。
三、封装公共请求头
在实际开发中,许多请求都需要携带公共请求头,例如设备信息、用户令牌等。你通过 CommonHeaderInterceptor
类实现了公共请求头的封装。
typescript
export async function getCommonHeaders(): Promise<Record<string, string>> {
return {
"device": LibDevice.getDeviceInfo(),
"machineCode": await USystem.getUniqueKey(),
"terminalType": "1", // 示例终端类型
"timestamp": new Date().toISOString(),
"versionNumber": LibDevice.getAppVersionCode().toString(),
"osId": Platform.appPlatform, // 示例 osId
"versionCode": LibDevice.getAppVersionName(),
"token": AccountManager.getToken()
};
}
export class CommonHeaderInterceptor implements rcp.Interceptor {
async intercept(context: rcp.RequestContext, next: rcp.RequestHandler): Promise<rcp.Response> {
const commonHeaders = await getCommonHeaders();
const keys = Object.keys(commonHeaders);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if (!context.request.headers) {
context.request.headers = {};
}
context.request.headers[key] = commonHeaders[key];
}
return next.handle(context);
}
}
核心点解析:
- 公共头信息 :通过
getCommonHeaders
函数获取公共头信息。 - 拦截器实现 :在
intercept
方法中,将公共头信息添加到请求头中。 - 动态添加:通过循环动态添加公共头信息,确保每个请求都携带必要的信息。
四、封装响应转换器
为了更好地处理响应数据,封装一个 CommonTextResponseConverter
类,用于处理响应内容。
typescript
export class CommonTextResponseConverter extends TextResponseConverter {
convert(response: rcp.Response): string | object | null {
if (response.toJSON()) {
return response.toJSON();
} else {
return super.convert(response);
}
}
}
核心点解析:
- JSON 转换:优先尝试将响应内容转换为 JSON 格式。
- 回退处理 :如果无法转换为 JSON,调用父类的
convert
方法处理响应内容。
五、实际应用案例:获取运动数据
假设我们需要从服务器获取用户的运动数据,例如运动记录、运动计划等。我们将通过封装好的 RCP 网络库来实现这一功能。
(一)封装请求方法
typescript
export async function getAllLookSubjectList(params: Record<"parentId", string>, requestKeyFun?: (str: string) => void): Promise<ApiResult<Subject[]>> {
return ApiRequest.getInstance().getService().request<ApiResult<Subject[]>>>({
act: AllLOOK_SUBJECT_LIST,
method: RequestMethod.POST,
contentType: RcpContentType.JSON,
content: params
}, requestKeyFun);
}
核心点解析:
- 请求配置 :通过
RequestOptions
配置请求的基本信息,如 API 路径、请求方法和内容类型。 - 请求发送 :使用
ApiRequest.getInstance().getService().request
方法发送请求。 - 回调函数 :通过
requestKeyFun
提供请求键值,用于取消请求等操作。
(二)调用请求方法
在实际的页面或组件中,我们可以调用封装好的请求方法来获取运动数据。
typescript
aboutToAppear(): void {
super.aboutToAppear();
this.showLoading();
ApiRequest.service<Subject[]>(() => {
return LookResponsitory.getInstance().getAllLookSubjectList("1", (requestKey) => {
if (requestKey) {
this.addRequestId(requestKey);
}
});
}, (data) => {
this.hideLoading();
if (data.data && data.data.length > 0) {
this.tabs = data.data;
} else {
this.setDefalutTab();
}
}, (apiError) => {
this.hideLoading();
this.setDefalutTab();
// 业务异常
// LibToast.show(apiError.message ?? "获取异常");
}, () => {
this.hideLoading();
this.setDefalutTab();
// 网络等异常
// LibToast.show("网络异常");
});
}
核心点解析:
- 请求执行 :通过
ApiRequest.service
方法执行网络请求。 - 成功处理:如果请求成功,处理返回的数据。
- 错误处理:根据错误类型调用相应的回调函数处理错误。
- 加载状态:在请求开始时显示加载状态,在请求结束时隐藏加载状态。
六、总结
通过本篇的实战案例,我们展示了如何使用封装好的 RCP 网络库实现具体的网络请求功能。通过定义异常类、封装请求方法、处理公共请求头和响应转换器,以及实现具体的请求逻辑,我们能够高效地完成网络请求任务。封装好的网络库不仅提供了基本的请求和响应处理功能,还具备错误处理、日志记录、会话管理和网络状态检测等高级特性,能够满足大多数网络请求场景的需求。