鸿蒙运动项目开发:封装超级好用的 RCP 网络库(上)—— 请求参数封装,类型转化器与日志记录篇
##鸿蒙核心技术##运动开发## Remote Communication Kit(远场通信服务)
在鸿蒙运动项目开发中,网络通信是不可或缺的一部分。无论是获取运动数据、同步用户信息,还是加载运动视频资源,都需要一个稳定、高效且易于使用的网络库。本文将带你深入探索如何封装一个超级好用的 RCP 网络库,帮助你在鸿蒙开发中轻松应对各种网络请求。本系列文章分为上,中,下三篇,分别介绍网络库的核心功能、高级特性以及实际应用案例。
前言
在移动应用开发中,网络请求是与后端服务交互的基础。一个优秀的网络库不仅需要提供基本的请求功能,还需要具备错误处理、日志记录、缓存管理等高级特性。鸿蒙系统提供了强大的 RCP(Remote Communication Protocol)模块,用于实现高效的网络通信。通过封装 RCP 模块,我们可以构建一个功能完备且易于使用的网络库,提升开发效率和应用性能。
一、网络库的核心功能:请求与响应处理
(一)请求参数的封装
在网络请求中,参数的处理是关键环节之一。我们需要将复杂的请求参数(如表单数据、JSON 对象等)转换为适合传输的格式。为此,我们定义了一个 QueryParamAppender 接口,并实现了 CustomQueryParamAppender 类,用于处理查询参数的拼接。
// 定义一个用于附加查询参数的接口
export interface QueryParamAppender {
  append(queryParams?: Map<string, number|string|boolean|Array<number> | Array<string> | Array<boolean> >|Record<string,string>| object |undefined): string|undefined;
}
import { QueryParamAppender } from "./QueryParamAppender";
export class CustomQueryParamAppender implements QueryParamAppender {
  append(queryParams?: Map<string, string | number | boolean | number[] | string[] | boolean[]> |Record<string,string> | object |undefined): string|undefined {
    if (queryParams === undefined || (queryParams instanceof Map && queryParams.size === 0) || (typeof queryParams === 'object' && Object.keys(queryParams).length === 0)) {
      return;
    }
    const paramsArray: string[] = [];
    // 使用 Object.entries() 将对象转换为键值对数组
    let values:[string,string|number|boolean|number[]|string[]|boolean[]][] = Object.entries(queryParams)
    for (const qp of values) {
      let key = qp[0]
      let value = qp[1]
      let encodedValue = '';
      if (Array.isArray(value)) {
        for (let i = 0; i < value.length; i++) {
          encodedValue += `${encodeURIComponent(`${key}[${i}]`)}=${encodeURIComponent(value[i].toString())}&`;
        }
        if (encodedValue.length > 0) {
          encodedValue = encodedValue.slice(0, -1); // 移除最后一个 '&'
        }
      } else {
        encodedValue = encodeURIComponent(key) + '=' + encodeURIComponent(value.toString());
      }
      paramsArray.push(encodedValue);
    }
    return paramsArray.join('&');
  }
}
核心点解析:
- 参数类型支持:支持多种类型的参数,包括字符串、数字、布尔值以及数组。
- 编码处理:使用 encodeURIComponent对参数进行编码,确保参数在 URL 中的合法性。
- 数组处理:对于数组类型的参数,通过索引进行拼接,例如 key[0]=value1&key[1]=value2。
(二)响应内容的转换
网络请求的响应内容通常需要根据不同的内容类型(如 JSON、文本等)进行转换。为此,我们定义了 RequestConverter 和 ResponseConverter 接口,并实现了多种转换器,例如 FormConverter、JsonConverter、TextResponseConverter 和 ObjectResponseConverter。
import { rcp } from "@kit.RemoteCommunicationKit";
import { JSONUtil } from "../../JSONUtil";
import { modelToForm } from "../NetUtils";
import { RcpContentType } from "../RcpService";
export interface RequestConverter {
  contentType():RcpContentType;
  convert(value: object|undefined): rcp.RequestContent;
}
/**
 * Form表单转换器
 */
export class FormConverter implements RequestConverter {
  contentType(): RcpContentType {
    return RcpContentType.FORM_URLENCODED
  }
  convert(value: object|undefined): rcp.RequestContent {
    return modelToForm(value);
  }
}
export class JsonConverter implements RequestConverter {
  contentType(): RcpContentType {
    return RcpContentType.JSON;
  }
  convert(value: object|undefined): rcp.RequestContent {
    if(value){
      return JSONUtil.toString(value);
    }
    return ''
  }
}
export interface ResponseConverter {
  contentType():RcpContentType;
  convert(response: rcp.Response): string|object|null;
}
/**
 * 原始文本转换器
 */
export class TextResponseConverter implements ResponseConverter {
  contentType(): RcpContentType {
    return RcpContentType.TEXT_PLAIN;
  }
  convert(response: rcp.Response): string|object|null {
    return response.toString();
  }
}
export class ObjectResponseConverter implements ResponseConverter{
  contentType(): RcpContentType {
    return RcpContentType.JSON;
  }
  convert(response: rcp.Response): string|object|null {
    return response.toJSON()
  }
}
核心点解析:
- 内容类型标识:通过 contentType()方法明确指定转换器支持的内容类型。
- JSON 转换:将 JavaScript 对象转换为 JSON 字符串。
- 文本响应处理:直接将响应内容转换为字符串。
(三)请求与响应的统一管理
为了更好地管理请求与响应,我们创建了 ConverterManager 类,用于注册和选择合适的转换器。
// src/main/ets/net/converter/ConverterManager.ts
import { rcp } from '@kit.RemoteCommunicationKit';
import { CustomErrorCode, LibError } from '../../LibError';
import { RcpContentType } from '../RcpService';
import { RequestConverter, ResponseConverter } from './RcpConverter';
/**
 * 内容类型与转换器映射
 */
export class ConverterManager {
  private requestConverters: RequestConverter[] = [];
  private responseConverters: ResponseConverter[] = [];
  private _requestFunc?: ((data: object|undefined, dataType: RcpContentType) => RequestConverter) | undefined;
  private _responseFunc?: ((response: rcp.Response) => ResponseConverter) | undefined;
  public set responseFunc(value: ((response: rcp.Response) => ResponseConverter) | undefined) {
    this._responseFunc = value;
  }
  public set requestFunc(value: ((data: object|undefined, dataType: string) => RequestConverter) | undefined) {
    this._requestFunc = value;
  }
  // 注册请求转换器
  public registerRequestConverter(converter: RequestConverter): void {
    this.requestConverters.push(converter);
  }
  // 注册响应转换器
  public registerResponseConverter(converter: ResponseConverter): void {
    this.responseConverters.push(converter);
  }
  /**
   * 根据请求数据自动选择转换器
   */
  selectRequestConverter(data: object|undefined,dataType: RcpContentType): rcp.RequestContent {
    if(this._requestFunc){
      return this._requestFunc(data , dataType).convert(data)
    }
    for(const request of this.requestConverters){
      if(request.contentType() == dataType){
        return request.convert(data);
      }
    }
    throw LibError.makeBusinessError(CustomErrorCode.NOT_FIND_REQUEST_CONVERTER_ERROR,"NOT_FIND_REQUEST_CONVERTER_ERROR")
  }
  /**
   * 根据响应头选择转换器
   */
  selectResponseConverter(response: rcp.Response,contentType: string | string[] | undefined): string|object|null {
    if(this._responseFunc){
      return this._responseFunc(response).convert(response)
    }
    let dataType = RcpContentType.TEXT_PLAIN
    if(contentType){
      if (contentType.includes('application/json')) {
        dataType = RcpContentType.JSON
      } else if (contentType.includes('text/plain')) {
        dataType = RcpContentType.TEXT_PLAIN
      } else if (contentType.includes('text/html')) {
        dataType = RcpContentType.TEXT_PLAIN
      }
    }
    for(const responseConver of this.responseConverters){
      if(responseConver.contentType() == dataType){
        return responseConver.convert(response);
      }
    }
    throw LibError.makeBusinessError(CustomErrorCode.NOT_FIND_RESPONSE_CONVERTER_ERROR,"NOT_FIND_RESPONSE_CONVERTER_ERROR")
  }
}
核心点解析:
- 转换器注册:通过 registerRequestConverter和registerResponseConverter方法,将转换器注册到管理器中。
- 转换器选择:根据请求或响应的内容类型,自动选择合适的转换器进行处理。
- 错误处理:如果找不到合适的转换器,抛出错误提示。
二、网络库的高级特性:拦截器与日志记录
(一)拦截器机制
拦截器是网络库中的一个重要特性,用于在请求发送和响应返回时插入自定义逻辑。我们实现了 LoggingInterceptor,用于记录请求和响应的详细信息。
import { rcp } from "@kit.RemoteCommunicationKit";
import { util } from "@kit.ArkTS";
import { appLogger } from "../../../app/Application";
export class LoggingInterceptor implements rcp.Interceptor {
  async intercept(context: rcp.RequestContext, next: rcp.RequestHandler): Promise<rcp.Response> {
    // 记录请求信息
    this.logRequest(context.request);
    // 调用下一个请求处理器
    const response = await next.handle(context);
    // 记录响应信息
    this.logResponse(response);
    return response;
  }
  private logRequest(request: rcp.Request) {
    const method = request.method;
    const url = request.url.href;
    const headers = request.headers;
    const body = request.content;
    appLogger.info(`[请求] ${method} ${url}`);
    appLogger.info(`[请求头] ${JSON.stringify(headers, null, 2)}`);
    if (body instanceof rcp.Form) {
      appLogger.info(`[请求体] ${JSON.stringify(body, null, 2)}`);
    } else {
      appLogger.info(`[请求体] ${body}`);
    }
  }
  private logResponse(response: rcp.Response) {
    const statusCode = response.statusCode;
    const headers = response.headers;
    const body = response.body;
    appLogger.info(`[响应] 状态码: ${statusCode}`);
    appLogger.info(`[响应头] ${JSON.stringify(headers, null, 2)}`);
    if (body) {
      try {
        const uint8Array = new Uint8Array(body);
        // 将 ArrayBuffer 转换为字符串
        const decoder = new util.TextDecoder();
        const bodyString = decoder.decodeToString(uint8Array);
        // 尝试解析为 JSON
        appLogger.logMultiLine(`[响应头] ${JSON.stringify(JSON.parse(bodyString), null, 2)}`)
      } catch (error) {
        appLogger.logMultiLine(`[响应体] ${body}`);
      }
    }
  }
}
核心点解析:
- 请求记录:记录请求的 URL、方法、头信息和请求体。
- 响应记录:记录响应的状态码、头信息和响应体。
- 日志格式化:使用 JSON.stringify格式化日志输出,便于阅读。
(二)日志记录的重要性
日志记录在网络请求调试中至关重要。通过记录请求和响应的详细信息,我们可以快速定位问题,例如:
- 请求参数是否正确传递。
- 响应数据是否符合预期。
- 网络请求是否超时或失败。
在实际开发中,建议在开发阶段启用详细的日志记录,而在生产环境中关闭或仅记录关键信息,以避免性能损耗。
三、网络库的配置与初始化
为了使网络库更加灵活,我们提供了 HttpConfig 类,用于配置连接超时时间、传输超时时间、并发请求限制等参数。
import { Timeout } from "./NetConstants";
// 定义明确的配置接口
export interface IHttpConfigOptions {
  connectTimeout?: number;
  transferTimeout?: number;
  maxConcurrentRequests?: number;
  security?: boolean;
}
export class HttpConfig {
  /**
   * 连接超时时间(毫秒)
   */
  connectTimeout: number;
  /**
   * 传输超时时间(毫秒)
   */
  transferTimeout: number;
  /**
   * 最大并发请求数量
   */
  maxConcurrentRequests: number;
  constructor(config?: IHttpConfigOptions) {
    this.connectTimeout = config?.connectTimeout ?? Timeout.CONNECT_TIME_OUT;
    this.transferTimeout = config?.transferTimeout ?? Timeout.TRANS_TIME_OUT;
    this.maxConcurrentRequests = config?.maxConcurrentRequests ?? 5;
    this.security = config?.security ?? true
  }
}
核心点解析:
- 默认值设置:通过 ??操作符,为配置项提供默认值。
- 灵活配置:允许开发者根据需求自定义超时时间、并发限制等参数。
在本篇中,我们详细介绍了网络库的核心功能,包括请求参数的封装、响应内容的转换以及拦截器与日志记录机制。这些功能为我们的网络库提供了坚实的基础。在接下来的中篇中,我们将继续深入探讨网络库的高级特性,包括错误处理、会话管理以及网络状态检测等,进一步提升网络库的健壮性和易用性。敬请期待!
- 0回答
- 0粉丝
- 0关注
- 鸿蒙运动项目开发:封装超级好用的 RCP 网络库(下)—— 实战应用
- 鸿蒙运动项目开发:封装超级好用的 RCP 网络库(中)—— 错误处理,会话管理与网络状态检测篇
- 鸿蒙next RCP网络请求工具类基础封装来了
- 鸿蒙运动项目开发:项目运行环境切换器
- 鸿蒙开发:切换至基于rcp的网络请求
- 鸿蒙Next网络请求HTTP和RCP的使用和对比
- HarmonyOS NEXT应用开发实战(封装比UniApp和小程序更简单好用的网络库)
- 鸿蒙next RCP网络请求工具类进阶版来了
- 鸿蒙封装日志库并支持跳转显示行号
- 鸿蒙实战开发:网络层的艺术——优雅封装与搭建指南(上)
- 《探索 HarmonyOS NEXT(5.0):开启构建模块化项目架构奇幻之旅 ——第三方库的使用:网络请求RCP、二次封装上下拉刷新、弹窗》
- HarmonyOS NEXT应用开发实战:十二、远场通信RCP简单好用的模块化封装
- 鸿蒙开发(二):使用ArkTS实现HTTP请求功能:GET与POST请求的封装
- HarmonyOS运动开发:如何选择并上传运动记录
- HarmonyOS应用开发实战,半天实现知乎日报项目(二、网络接口的封装使用)

