HarmonyOS NEXT应用开发实战:十二、远场通信RCP简单好用的模块化封装
在进行HarmonyOS的应用开发中,我们常常需要进行网络通信。然而,原始的远场通信(RCP)使用方式较为繁琐,让人感到不够便捷。作为一位前期从事小程序开发的开发者,我深受小程序网络访问的简单性和便利性的吸引。因此,我决定在HarmonyOS中打造一个高效的网络组件,简化网络请求的使用方式。
原始使用方式的复杂性
首先,让我们看一下原始的RCP使用方式:
// 定义请求头
let headers: rcp.RequestHeaders = {
  'accept': 'application/json'
};
// 定义要修改的内容
let modifiedContent: UserInfo = {
  'userName': 'xxxxxx'
};
const securityConfig: rcp.SecurityConfiguration = {
  tlsOptions: {
    tlsVersion: 'TlsV1.3'
  }
};
// 创建通信会话对象
const session = rcp.createSession({ requestConfiguration: { security: securityConfig } });
// 定义请求对象
let req = new rcp.Request('http://example.com/fetch', 'PATCH', headers, modifiedContent);
// 发起请求
session.fetch(req).then((response) => {
  Logger.info(`Request succeeded, message is ${JSON.stringify(response)}`);
}).catch((err: BusinessError) => {
  Logger.error(`err: err code is ${err.code}, err message is ${JSON.stringify(err)}`);
});
从上面的代码来看,整个流程涉及多步骤的配置,包括请求头、请求体的定义,以及会话的创建。这样的流程虽然功能齐全,但对于开发者来说,无疑增加了工作量。
模块化封装后的优势
为了提升开发效率,我着手对RCP进行模块化封装。以下是封装后的核心代码:
import { rcp } from '@kit.RemoteCommunicationKit';
import { BusinessError } from '@kit.BasicServicesKit';
type AnyObject = Record<string | number | symbol, any>;
export type HttpPromise<T> = Promise<HttpResponse<T>>;
export interface RequestTask {
  abort: () => void;
  offHeadersReceived: () => void;
  onHeadersReceived: () => void;
}
type Tasks = RequestTask;
export interface HttpRequestConfig<T = Tasks> {
  /** 请求基地址 */
  baseURL?: string;
  /** 请求服务器接口地址 */
  url?: string;
  /** 请求查询参数,自动拼接为查询字符串 */
  params?: AnyObject;
  /** 请求体参数 */
  data?: AnyObject;
  /** 文件对应的 key */
  name?: string;
  /** HTTP 请求中其他额外的 form data */
  formData?: AnyObject;
  /** 要上传文件资源的路径。 */
  filePath?: string;
  /** 请求头信息 */
  header?: AnyObject;
  /** 请求方式 */
  method?: "GET" | "POST" | "PUT" | "DELETE" | "CONNECT" | "HEAD" | "OPTIONS" | "TRACE" | "UPLOAD" | "DOWNLOAD" | (string & NonNullable<unknown>);
  /** 如果设为 json,会尝试对返回的数据做一次 JSON.parse */
  dataType?: string;
  /** 设置响应的数据类型,支付宝小程序不支持 */
  responseType?: "text" | "arraybuffer";
  /** 自定义参数 */
  custom?: AnyObject;
  /** 超时时间,仅微信小程序(2.10.0)、支付宝小程序支持 */
  timeout?: number;
  /** DNS解析时优先使用ipv4,仅 App-Android 支持 (HBuilderX 2.8.0+) */
  firstIpv4?: boolean;
  /** 验证 ssl 证书 仅5+App安卓端支持(HBuilderX 2.3.3+) */
  sslVerify?: boolean;
  /** 跨域请求时是否携带凭证(cookies)仅H5支持(HBuilderX 2.6.15+) */
  withCredentials?: boolean;
  /** 返回当前请求的task, options。请勿在此处修改options。 */
  getTask?: (task: T, options: HttpRequestConfig<T>) => void;
  /**  全局自定义验证器 */
  validateStatus?: (statusCode: number) => boolean | void;
}
export interface HttpResponse<T = any> {
  config?: HttpRequestConfig;
  statusCode: number;
  data: T;
  errMsg: string;
  cookies: Array<string>;
  header: AnyObject;
}
export interface HttpUploadResponse<T = any> {
  config: HttpRequestConfig;
  statusCode: number;
  data: T;
  errMsg: string;
}
export interface HttpDownloadResponse extends HttpResponse {
  tempFilePath: string;
}
export abstract class HttpRequestAbstract {
  session: rcp.Session;
  config: HttpRequestConfig;
  constructor(public conf: HttpRequestConfig) {
    this.config = {
      ...conf,
      validateStatus: conf.validateStatus || ((status) => status >= 200 && status < 300)
    };
    const sessionConfig: rcp.SessionConfiguration = {
      baseAddress: conf.baseURL,
      headers: conf.header,
      requestConfiguration: {
        security: {
          tlsOptions: {
            tlsVersion: 'TlsV1.3'
          }
        }
      }
    }
    this.session = rcp.createSession(sessionConfig);
  }
  request<T = any>(config: HttpRequestConfig<RequestTask>): HttpPromise<T> {
    return new Promise((resolve, reject) => {
      const { method = "GET", url, header, data,params } = config;
      const fullUrl =`${this.config.baseURL}${url}`;
      
      const req = new rcp.Request(fullUrl, method, header, data);
      
      this.session.fetch(req).then((response) => {
        const responseData = response.toJSON();
        resolve({ data: responseData, statusCode: response.statusCode });
      }).catch((err: BusinessError) => {
        reject(err);
      });
    });
  }
  // GET 请求
  get<T = any>(url: string, config?: HttpRequestConfig<RequestTask>): HttpPromise<T> {
    return this.request({ ...config, url, method: 'GET' });
  }
  // POST 请求
  post<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<RequestTask>): HttpPromise<T> {
    return this.request({ ...config, url, data, method: 'POST' });
  }
  // PUT 请求
  put<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<RequestTask>): HttpPromise<T> {
    return this.request({ ...config, url, data, method: 'PUT' });
  }
  // DELETE 请求
  delete<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<RequestTask>): HttpPromise<T> {
    return this.request({ ...config, url, data, method: 'DELETE' });
  }
  // 其他 HTTP 请求方法可以类似实现,如 head、options、trace 等
  // 上传
  upload<T = any>(url: string, config?: HttpRequestConfig<RequestTask>): Promise<HttpUploadResponse>{
    return new Promise((resolve, reject) => {
      let fileDir = config.filePath; // 请根据自身业务定义此路径
      let uploadFromFile : rcp.UploadFromFile = {
        fileOrPath : fileDir
      }
      this.session.uploadFromFile(url, uploadFromFile).then((response) => {
        console.info(`Succeeded in getting the response ${response}`);
        let resp = {
          config,
          data: response.toJSON() as T,
          statusCode: response.statusCode,
          errMsg: response.reasonPhrase
        };
        resolve(resp)
      }).catch((err: BusinessError) => {
        console.error(`err: err code is ${err.code}, err message is ${JSON.stringify(err)}`);
        reject(err)
      });
    })
  }
  // 下载
  download(url: string, config?: HttpRequestConfig<RequestTask>): Promise<HttpDownloadResponse>{
    return new Promise((resolve, reject) => {
      let downloadToFile: rcp.DownloadToFile = {
        kind: 'folder',
        path: config.filePath //请根据自身业务选择合适的路径
      } as rcp.DownloadToFile
      this.session.downloadToFile(url, downloadToFile).then((response) => {
        console.info(`Succeeded in getting the response ${response}`);
        let resp = {
          config,
          data: response.toJSON(),
          statusCode: response.statusCode,
          errMsg: response.reasonPhrase,
          tempFilePath:config.filePath,
          header:response.headers,
          cookies:response.cookies?.map(cookie => `${cookie.name}=${cookie.value}`),
        };
        resolve(resp)
      }).catch((err: BusinessError) => {
        console.error(`DownloadToFile failed, the error message is ${JSON.stringify(err)}`);
        reject(err)
      });
    })
  }
}
通过这样的封装,我们将网络请求的逻辑进行了抽象,简化了外部调用的复杂性。开发者只需专注于请求的参数,而无需关心底层的实现。
封装后的简单使用
在封装完成后,发起网络请求的代码大幅度简化,例如:
//utils/http.ts
import HttpRequest, { HttpPromise,HttpRequestConfig, HttpResponse } from './core';
const config:HttpRequestConfig = {
  baseURL: "http://175.178.126.10:8000/",
  validateStatus: (status) => {
    return status >= 200 && status < 300;
  }
}
export const httpClient = new HttpRequest(config);
export const setRequestConfig = () => {
  // 请求拦截
  httpClient.requestInterceptor.onFulfilled = (config?: HttpRequestConfig) =>{
    // 返回一个符合 HttpRequestConfig 类型的对象
    console.debug('请求拦截')
    return {
    }
  }
  httpClient.responseInterceptor.onFulfilled = (response?: HttpResponse) =>{
    // 返回一个符合 HttpResponse 类型的对象
    console.debug('响应拦截')
    return {
    }as HttpResponse
  }
}
export {HttpPromise};
export default httpClient;
// homeapi.ts
import { httpClient, HttpPromise, setRequestConfig } from '../../utils/http';
// 调用setRequestConfig函数进行请求配置
setRequestConfig();
const http = httpClient;
// 获取轮播图api接口
export const getSwiperList = (): HttpPromise<BaseResponse<SwiperData>> => http.get('/swiperdata');
// 获取热门影视接口
export const getHotMovie = (req: HotMovieReq): HttpPromise<BaseResponse<MovieRespData>> => http.post('/hotmovie', req);
// 获取知乎列表页api接口
export const getZhiHuNews = (date:string): HttpPromise<BaseResponse<ZhiNewsRespData>> => http.get('/zhihunews/'+date);
// 获取知乎详情页api接口
export const getZhiHuDetail = (id:string): HttpPromise<BaseResponse<ZhiDetailRespData>> => http.get('/zhihudetail/'+id);
这一改动使得接口调用变得极其简单,清晰易读,减少了出错的可能性。只需两行代码即可写完两个接口,完成复杂的网络请求,极大提高了开发效率。
总结
通过对HarmonyOS中远场通信RCP的模块化封装,我们不仅优化了网络请求的流程,还提升了代码的可读性和可维护性。希望这篇文章能够帮助你在HarmonyOS应用开发中更高效地使用网络组件,享受更便捷的开发体验。
写在最后
最后,推荐下笔者的业余开源app影视项目“爱影家”,推荐分享给与我一样喜欢免费观影的朋友。 注:因涉及免费观影,该项目仅限于学习研究使用!请勿用于其他用途!
开源地址:爱影家app开源项目介绍及源码
其他资源
https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V13/remote-communication-interceptor-V13 https://developer.huawei.com/consumer/cn/doc/best-practices-V5/bpta-rcp-based-network-request-V5
- 4回答
- 7粉丝
- 5关注
- 鸿蒙开发:远场通信服务rcp会话问题
- 鸿蒙开发,远场通信服务rcp拦截器问题
- 鸿蒙运动项目开发:封装超级好用的 RCP 网络库(下)—— 实战应用
- HarmonyOS NEXT应用开发实战(封装比UniApp和小程序更简单好用的网络库)
- (四二)HarmonyOS Design 的模块化设计原则
- 《探索 HarmonyOS NEXT(5.0):开启构建模块化项目架构奇幻之旅 —— 模块化基础篇》
- HarmonyOS NEXT:模块化项目 ——修改应用图标+启动页等
- 《探索 HarmonyOS NEXT (5.0):开启构建模块化项目架构奇幻之旅 —— 动态路由 ZRouter:引领高效模块通信的智慧中枢》
- 鸿蒙Next模块化设计实战:反射调用实现动态功能组合
- 鸿蒙运动项目开发:封装超级好用的 RCP 网络库(上)—— 请求参数封装,类型转化器与日志记录篇
- HarmonyOS NEXT模块化设计实践:打造简洁高效的登录注册页面
- 《探索 HarmonyOS NEXT(5.0):开启构建模块化项目架构奇幻之旅 ——第三方库的使用:网络请求RCP、二次封装上下拉刷新、弹窗》
- HarmonyOS 5应用分层模块化实践:从架构设计到多端部署
- (三五)HarmonyOS Design 的模块化开发:优势与实践
- 鸿蒙运动项目开发:封装超级好用的 RCP 网络库(中)—— 错误处理,会话管理与网络状态检测篇

