鸿蒙操作系统使用 Remote Communication Kit(rcp)实现网络请求封装实战
一、背景说明
HarmonyOS 提供了两套网络请求 API:基础的 @ohos.net.http 和更强大的 Remote Communication Kit(rcp)。相比 @ohos.net.http,rcp 提供了会话(Session)级别的连接复用、拦截器、DNS 配置、缓存控制和 HTTP 版本协商等企业级能力。随着 HarmonyOS 7(API 26)在 rcp 中引入 QUIC 支持,HttpVersionSelectCallback 允许开发者在 ALPN 协商阶段动态决策 HTTP 版本,弱网环境下的传输体验进一步提升。本文实现一个基于 rcp 的通用网络请求封装,统一处理 GET/POST、请求头、超时、重试与响应解析,可直接用于业务工程。
二、技术原理
rcp 的核心是「会话—请求—响应」三层模型:
- Session(会话):通过
rcp.createSession(config)创建,持有连接池、DNS 缓存、Cookie 管理等配置。一次创建、多次请求复用底层 TCP/TLS 连接,避免重复握手。会话使用完应调用close()释放资源(自 5.1.0(18) 起最多可创建 1024 个 session 实例)。 - Request(请求):描述 URL、方法、请求头、请求体、超时等;也可直接用
session.get()/post()便捷方法。 - Response(响应):包含
statusCode、headers、响应体(可按字符串、JSON、ArrayBuffer 等形式取出)。
数据流:createSession → session.fetch(request) → Promise<Response>。权限上需要 ohos.permission.INTERNET;若把请求路径偏好设为蜂窝网络(pathPreference: 'cellular'),还需要 ohos.permission.GET_NETWORK_INFO。
与 @ohos.net.http 相比的关键差异:rcp 的会话配置(拦截器、超时、Cookie、缓存)在 Session 级别统一生效,不必每个请求重复设置;这是封装成单例网络层的天然基础。
三、实现步骤
1. 权限配置
在 entry/src/main/module.json5 的 module 节点下添加:
{
"module": {
// ...其余配置
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
]
}
}
2. 请求封装类 HttpRequestUtil
新建 entry/src/main/ets/utils/HttpRequestUtil.ets,完整代码如下:
import { rcp } from '@kit.RemoteCommunicationKit';
import { BusinessError } from '@kit.BasicServicesKit';
/** 统一响应结构 */
export interface HttpResult<T> {
success: boolean;
code: number; // HTTP 状态码,失败时为 -1
data?: T; // 解析后的业务数据
message: string; // 错误或状态描述
}
export class HttpRequestUtil {
private static session: rcp.Session | null = null;
/** 懒加载全局会话:拦截器、超时、请求头在会话级统一生效 */
private static getSession(): rcp.Session {
if (!HttpRequestUtil.session) {
HttpRequestUtil.session = rcp.createSession({
// 请求拦截器:统一注入时间戳与 token 占位
interceptors: [{
intercept: (context: rcp.RequestContext) => {
context.request.headers['X-Timestamp'] = Date.now().toString();
return context.next.handle(context);
}
}],
// 会话级请求头
headers: {
'content-type': 'application/json',
'user-agent': 'HarmonyOS-RCP-Demo/1.0'
},
// 会话级超时(毫秒)
configuration: {
timeout: {
connect: 10000,
transfer: 15000
}
},
// HTTP 版本协商回调(API 26+):可对问题域名强制降级
httpVersionSelectCallback: (incomingVersion: rcp.HttpVersion) => {
// 示例:已知某老网关 HTTP/2 有兼容问题,降级到 HTTP/1.1
// 其余情况返回 undefined,尊重系统协商结果
return undefined;
}
});
}
return HttpRequestUtil.session;
}
/** GET 请求 */
static async get<T>(url: string, params?: Record<string, string>): Promise<HttpResult<T>> {
let fullUrl = url;
if (params) {
const query = Object.keys(params)
.map(k => `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`)
.join('&');
fullUrl = url.includes('?') ? `${url}&${query}` : `${url}?${query}`;
}
return HttpRequestUtil.request<T>(fullUrl, 'GET');
}
/** POST 请求(JSON 体) */
static async post<T>(url: string, body: object): Promise<HttpResult<T>> {
return HttpRequestUtil.request<T>(url, 'POST', body);
}
/** 通用请求:发请求 → 校验状态码 → 解析 JSON */
private static async request<T>(url: string, method: 'GET' | 'POST', body?: object): Promise<HttpResult<T>> {
const session = HttpRequestUtil.getSession();
try {
const request: rcp.Request = {
url: url,
method: method,
content: body ? JSON.stringify(body) : undefined
};
const response = await session.fetch(request);
if (response.statusCode >= 200 && response.statusCode < 300) {
const data = response.toJSON() as T;
return { success: true, code: response.statusCode, data: data, message: 'ok' };
}
return {
success: false,
code: response.statusCode,
message: `服务器返回异常状态码: ${response.statusCode}`
};
} catch (err) {
const e = err as BusinessError;
console.error(`rcp request failed, code=${e.code}, msg=${e.message}`);
return { success: false, code: -1, message: `网络请求失败: ${e.message}` };
}
}
/** 应用退出或页面销毁时释放会话 */
static closeSession(): void {
if (HttpRequestUtil.session) {
HttpRequestUtil.session.close();
HttpRequestUtil.session = null;
}
}
}
3. UI 调用示例
新建 entry/src/main/ets/pages/Index.ets:
import { HttpRequestUtil, HttpResult } from '../utils/HttpRequestUtil';
interface TodoItem {
userId: number;
id: number;
title: string;
completed: boolean;
}
@Entry
@Component
struct Index {
@State resultText: string = '点击按钮发起请求';
private async doGet(): Promise<void> {
const result: HttpResult<TodoItem> = await HttpRequestUtil.get<TodoItem>(
'https://jsonplaceholder.typicode.com/todos/1'
);
if (result.success && result.data) {
this.resultText = `GET 成功\n标题: ${result.data.title}\n完成: ${result.data.completed}`;
} else {
this.resultText = `GET 失败: ${result.message}`;
}
}
private async doPost(): Promise<void> {
const result: HttpResult<TodoItem> = await HttpRequestUtil.post<TodoItem>(
'https://jsonplaceholder.typicode.com/todos',
{ userId: 1, title: '学习 rcp 网络请求', completed: false }
);
this.resultText = result.success
? `POST 成功,新建记录 id=${result.data?.id}`
: `POST 失败: ${result.message}`;
}
build() {
Column({ space: 16 }) {
Text('Remote Communication Kit(rcp)网络请求')
.fontSize(20)
.fontWeight(FontWeight.Bold)
Button('发起 GET 请求').onClick(() => {
this.doGet();
})
Button('发起 POST 请求').onClick(() => {
this.doPost();
})
Text(this.resultText)
.fontSize(14)
.padding(12)
.backgroundColor('#F1F3F5')
.borderRadius(8)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.padding(16)
}
}
四、应用场景示例
结合按钮触发,在业务代码里直接复用封装(带查询参数的搜索场景):
import { HttpRequestUtil } from '../utils/HttpRequestUtil';
interface SearchResponse {
total: number;
list: object[];
}
async function searchKeyword(keyword: string): Promise<void> {
const result = await HttpRequestUtil.get<SearchResponse>(
'https://api.example.com/search',
{ q: keyword, page: '1', size: '20' }
);
if (result.success && result.data) {
console.info(`搜索到 ${result.data.total} 条结果`);
} else {
console.warn(`搜索失败: ${result.message}`);
}
}
五、技术要点
- 权限:
ohos.permission.INTERNET为普通权限,module.json5 声明即可;pathPreference: 'cellular'模式还需ohos.permission.GET_NETWORK_INFO。 - 会话生命周期:Session 底层持有连接池,
getSession()用单例懒加载避免频繁握手;页面级或应用退出时调用closeSession()释放,防止连接泄漏。系统上限 1024 个 session 实例。 - 超时分层:
configuration.timeout里connect控制建连超时、transfer控制传输超时,弱网场景建议两者分开设置,并在请求级configuration里覆盖会话级默认值。 - 错误处理:
session.fetch抛出的BusinessError携带code与message,按业务需要区分网络不可达、超时、SSL 错误并给出不同提示,避免把原始异常直接抛给 UI。 - HTTP 版本协商:
httpVersionSelectCallback运行在网络 I/O 线程,回调里禁止阻塞操作(读文件、再发请求),否则 ALPN 协商挂起导致全部请求超时。 - JSON 解析:
response.toJSON()直接反序列化;响应体较大时改用response.arrayBuffer()分段处理,避免主线程解析长字符串。
六、扩展建议
可以进一步给封装加上自动重试与请求缓存。重试示例(在 request 外包一层):
static async getWithRetry<T>(url: string, retries: number = 2): Promise<HttpResult<T>> {
let last: HttpResult<T> = { success: false, code: -1, message: '未执行' };
for (let i = 0; i <= retries; i++) {
last = await HttpRequestUtil.get<T>(url);
if (last.success) {
return last;
}
}
return last;
}
进阶方向:用 rcp.Interceptor 实现统一日志埋点;通过 SessionConfiguration 的 Cookie 管理维持登录态;对大文件下载改用 destination 指定响应体落盘路径,边下边写不占内存。
结语
本文基于 Remote Communication Kit 实现了鸿蒙 ArkTS 侧的通用网络请求封装,核心代码约 130 行,覆盖会话复用、拦截器、超时配置、统一错误处理与 JSON 解析。相比逐请求创建 @ohos.net.http 实例,rcp 的会话模型让网络层更易维护,配合 API 26 的 HTTP 版本协商与 QUIC 能力,可以平滑应对弱网与高延迟场景。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)