嗨,朋友们!今天咱们来聊一个在 HarmonyOS 开发中超级实用的东西 – 远场通信服务(Remote Communication Kit)。不管你是想从服务器拉数据、提交表单,还是做断点续传下载大文件,这套东西都能帮你搞定。

说实话,我刚接触 HarmonyOS NEXT 开发的时候,最头疼的就是网络请求。之前在 Android 里用 OkHttp,在 Web 里用 fetch/axios,到了鸿蒙这一看,怎么发个 HTTP 请求都不太一样了?后来研究了华为官方的远场通信服务,才发现原来事情可以这么简单。

所以今天这篇文章,我就手把手带着大家,把远场通信服务的核心功能全部过一遍。看完这篇,你就能在自己的鸿蒙应用里自如地发起网络请求了。

咱们要覆盖的内容包括:

  • 基础 URL 配置 – 配一个基础地址,后续请求直接用相对路径
  • 连接超时和传输超时 – 别让请求一直挂着等不到结果
  • 断点续传 – 大文件下载断了一半?没关系,接上继续

准备好了吗?那我们开始吧!


一、远场通信服务是个啥?

在正式写代码之前,咱们先花一分钟搞清楚远场通信服务到底是个什么东西。

官方的说法是这样的:

远场通信服务(Remote Communication Kit)是华为提供的 HTTP 发起数据请求的 NAPI 封装。应用通过远场通信服务可便捷快速地向服务器发起数据请求。

翻译成人话就是:这是一套帮你发 HTTP 请求的工具库。你不需要自己去处理底层的 socket 连接、协议解析这些脏活累活,调用几个 API 就行了。

核心使用方式就三步:

  1. rcp.createSession() – 创建一个会话(session)
  2. session.get() / session.fetch() – 发请求
  3. 处理返回结果

对,就这么简单。咱们往下看具体的代码。


二、整体架构 – 四个页面都在干什么?

在本次教程中,我们一共会创建 四个页面,每个页面负责演示远场通信服务的一个功能点:

页面 文件名 功能说明
主界面 MainPage 提供导航,跳转到三个测试页面
基础 URL 测试 BaseAddress 演示如何配置和使用 baseAddress
超时测试 TimeOut 演示连接超时(connectMs)和传输超时(transferMs)
断点续传 TransferRange 演示断点续传功能(TransferRange)

在上面的动图里可以看到,整个应用的运行效果还是很直观的。主界面有一个按钮,点击后向百度发送请求并展示返回结果。

好,咱们一个一个页面来写代码。


三、主界面(MainPage)-- 简单的导航页

主界面的功能很简单:展示几个按钮,点击后跳转到对应的测试页面。但实际上在教程代码里,主界面本身也内嵌了一个 GET 请求测试的功能。咱们先来看完整的代码,然后逐行解析。

3.1 完整代码

import { common } from '@kit.AbilityKit';
import { rcp } from '@kit.RemoteCommunicationKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { notificationManager } from '@kit.NotificationKit';

const kHttpServerAddress = "https://httpbin.org";
@Entry
@Component
struct  requestContent{
@State content:string=''
async aboutToAppear() {
this.openSysSwitch();
}

build() {
Flex({ direction: FlexDirection.Column }) {
Row() {
Text('BaseAddress')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#ff100f0f')
.height(30)
.width(220)
.margin({ left: 4 })
}
.height(32)
.width(124)
.margin({ top: 32, left: 12 })

Divider().color('gray').strokeWidth(1)
Flex({ direction: FlexDirection.Column,
justifyContent: FlexAlign.SpaceBetween }) {

Button('baseAddress-Test')
.width('80%')
.height(40)
.margin({ left: '10%' })
.backgroundColor('#0A59F7')
.fontColor('#ffffffff')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.borderRadius(20)
.onClick(this.getTokenSyn)
}
.height(64)
.margin({ top: 86 })

Text("结果")
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#000')
.height(30)
.width(220)
.margin({ left: 8 })
TextArea({
text: this.content
})
.width('80%')
.height(260)
.margin({ left: '10%',top: '10%' })
.backgroundColor('0x317aff')
.fontColor('#ffffffff')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.borderRadius(10)
}
.linearGradient({
direction: GradientDirection.Top,
repeating: true,
colors: [['#FFFFFF', 0], ['#FFFFFF', 1]]
})
.height('100%')
.width('100%')
}

private getTokenSyn = async () => {
try {
let session = rcp.createSession({
baseAddress: kHttpServerAddress
});
const resp = await session.get("https://www.baidu.com");
console.info(`${kHttpServerAddress} 传递成功 ${resp}`)
this.content=`${kHttpServerAddress} 传递成功 ${resp}`
} catch (err) {
let e: BusinessError = err as BusinessError;
console.error(`${kHttpServerAddress} 传递失败`+ JSON.stringify(err))
this.content=`${kHttpServerAddress} 传递失败`+ JSON.stringify(err)
}
}

private openSysSwitch = async () => {
let context = getContext(this) as common.UIAbilityContext;
await notificationManager.requestEnableNotification(context);
console.info('testTag', 'System notification switch is open');
}
}

3.2 代码逐行解读

来,咱们一行一行地看这段代码到底在干什么。

第一部分:导入依赖

import { common } from '@kit.AbilityKit';
import { rcp } from '@kit.RemoteCommunicationKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { notificationManager } from '@kit.NotificationKit';

这里导入了四个模块:

  • @kit.AbilityKitcommon – 获取 UIAbility 上下文用的
  • @kit.RemoteCommunicationKitrcp这个就是远场通信服务的核心,所有网络请求相关的 API 都在这里面
  • @kit.BasicServicesKitBusinessError – 业务错误类型,用来在 catch 里做类型断言
  • @kit.NotificationKitnotificationManager – 通知管理器,用来请求开启系统通知

第二部分:常量和组件定义

const kHttpServerAddress = "https://httpbin.org";
@Entry
@Component
struct  requestContent{
@State content:string=''
async aboutToAppear() {
this.openSysSwitch();
}

kHttpServerAddress 是我们定义的服务器地址常量。这里用的是 httpbin.org,这是一个专门用来测试 HTTP 请求的公开服务,非常好用。

requestContent 是我们的主组件,里面有一个 @State content 状态变量,用来存储请求结果并显示在页面上。aboutToAppear 是组件即将出现时的生命周期回调,在这里我们调用了 openSysSwitch() 方法来请求开启系统通知权限。

第三部分:UI 布局

UI 部分比较直观,就是一个纵向的 Flex 布局,从上到下依次是:

  1. 标题文字 “BaseAddress”
  2. 分割线
  3. 一个蓝色按钮 “baseAddress-Test”
  4. “结果” 文字标签
  5. 一个文本区域(TextArea)用来显示请求结果

样式方面用了一些圆角、渐变背景之类的,看起来还算清爽。

第四部分:网络请求核心逻辑

private getTokenSyn = async () => {
try {
let session = rcp.createSession({
baseAddress: kHttpServerAddress
});
const resp = await session.get("https://www.baidu.com");
console.info(`${kHttpServerAddress} 传递成功 ${resp}`)
this.content=`${kHttpServerAddress} 传递成功 ${resp}`
} catch (err) {
let e: BusinessError = err as BusinessError;
console.error(`${kHttpServerAddress} 传递失败`+ JSON.stringify(err))
this.content=`${kHttpServerAddress} 传递失败`+ JSON.stringify(err)
}
}

这里是重点!当你点击按钮时,会触发 getTokenSyn 方法:

  1. rcp.createSession({ baseAddress: kHttpServerAddress }) – 创建一个会话,同时配置了 baseAddresshttps://httpbin.orgbaseAddress 是一个基础 URL,后续在会话中发请求时,如果传入的是相对路径,就会自动拼上这个基础地址。
  2. session.get("https://www.baidu.com") – 向百度发起 GET 请求,获取响应。
  3. 成功时:将响应结果通过 this.content 显示在页面上。
  4. 失败时:将错误信息捕获并通过 BusinessError 类型转换后,也显示在页面上。

第五部分:通知权限

private openSysSwitch = async () => {
let context = getContext(this) as common.UIAbilityContext;
await notificationManager.requestEnableNotification(context);
console.info('testTag', 'System notification switch is open');
}

这段代码的作用是在页面出现时请求用户开启系统通知。虽然在这个 Demo 里看起来跟网络请求没什么关系,但在实际项目中,网络请求完成后的结果通知、后台任务提醒等都可能用到通知功能,所以这里提前准备好了。

小提示:在实际开发中,如果你的应用不需要通知功能,这段代码可以省略。不过有些场景下(比如下载完成后通知用户),通知还是很方便的。


四、基础 URL 测试页面(BaseAddress)-- baseAddress 的正确用法

主界面里其实已经演示了 baseAddress 的用法,但那个页面混了其他东西。BaseAddress 页面则更加纯粹,专门用来演示如何配置和使用 baseAddress

4.1 为什么要用 baseAddress?

想象一下这个场景:你的应用要跟同一个后端服务器打交道,所有的 API 地址都是 https://api.example.com/xxx。如果你每次请求都写完整的 URL,那代码里到处都是 https://api.example.com,多累啊。

有了 baseAddress,你只需要在创建会话时设一次,之后发请求只需要写相对路径就行了。比如 session.get("/users") 就相当于 session.get("https://api.example.com/users")

清爽多了对吧?

4.2 完整代码

import { hilog } from '@kit.PerformanceAnalysisKit';
import { rcp } from '@kit.RemoteCommunicationKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { notificationManager } from '@kit.NotificationKit';
import { common } from '@kit.AbilityKit';

const kHttpServerAddress = "https://httpbin.org"; //访问的网址
@Entry
@Component
struct  requestContent{
@State content:string=''
async aboutToAppear() {
this.openSysSwitch();
}
//当前UI界面配置
build() {
}

//封装一个方法,调用接口
private getTokenSyn = async () => {
try {
let session = rcp.createSession({
baseAddress: kHttpServerAddress
});
const resp = await session.get("");
hilog.info(0x0000, 'testTag', 'Get push token successfully: %{public}s', JSON.stringify(resp));
console.info(`${kHttpServerAddress} 传递成功`+ JSON.stringify(resp))
this.content=`${kHttpServerAddress} 传递成功`+ JSON.stringify(resp)
} catch (err) {
let e: BusinessError = err as BusinessError;
hilog.error(0x0000, 'testTag', 'Get push token catch error: %{public}d %{public}s', e.code, e.message);
console.error(`${kHttpServerAddress} 传递失败`+ JSON.stringify(err))
this.content=`${kHttpServerAddress} 传递失败`+ JSON.stringify(err)
}
}

private openSysSwitch = async () => {
let context = getContext(this) as common.UIAbilityContext;
await notificationManager.requestEnableNotification(context);
hilog.info(0x0000, 'testTag', 'System notification switch is open');
}
}

4.3 代码解析

这个页面的代码跟主界面很相似,但有几个值得注意的区别:

1. 新增了 hilog 日志

import { hilog } from '@kit.PerformanceAnalysisKit';

hilog 是 HarmonyOS 提供的高性能日志系统。相比 console.log,hilog 支持日志级别、标签、格式化输出等功能,更适合在正式项目中使用。在这个页面里,我们同时用了 hilogconsole,你可以根据自己的习惯选择。

看这行:

hilog.info(0x0000, 'testTag', 'Get push token successfully: %{public}s', JSON.stringify(resp));
  • 第一个参数 0x0000 是日志领域(domain)
  • 第二个参数 'testTag' 是日志标签
  • 第三个参数是日志格式字符串,%{public}s 表示一个公开的字符串占位符
  • 第四个参数是实际的值

2. 请求地址为空字符串

const resp = await session.get("");

注意这里 session.get("") 传的是空字符串!因为我们已经通过 baseAddress 配置了 https://httpbin.org,所以这里传空字符串就相当于直接请求 https://httpbin.org 本身。httpbin.org 的根路径会返回一些基本信息,刚好可以用来验证请求是否成功。

这就是 baseAddress 的魅力所在 – 你配置好基础地址后,相对路径可以传空字符串、传 /get、传 /post,都非常方便。

3. build 方法为空

build() {
}

嗯,教程原文里 BaseAddress 页面的 build 方法是空的。这意味着 UI 布局需要在实际项目中自己补充。不过核心的网络请求逻辑是完整的,你可以参考主界面的布局来搭建自己的 UI。


五、超时测试页面(TimeOut)-- 别让请求永远等下去

有没有遇到过这种情况:你发了一个网络请求,结果对方服务器半天没响应,你的应用就卡在那里了。用户体验极差对吧?

这时候就需要设置超时时间了。远场通信服务支持两种超时:

  • connectMs(连接超时):建立 TCP 连接的最大等待时间(毫秒)。如果在这段时间内连不上服务器,就直接报错。
  • transferMs(传输超时):数据传输的最大等待时间(毫秒)。连接建立成功后,如果数据传输过程中超过这个时间没有数据往来,就报错。

5.1 完整代码

import { rcp } from '@kit.RemoteCommunicationKit';
import { notificationManager } from '@kit.NotificationKit';
import { common } from '@kit.AbilityKit';

const kHttpServerAddress = "https://httpbin.org";
@Entry
@Component
struct  requestContent{
@State showToken: string = '';
@State content: string = '';
@State timeout:rcp.Timeout = {
connectMs: 1000,
transferMs: 1000
}
async aboutToAppear() {
this.openSysSwitch();
}

build() {
Flex({ direction: FlexDirection.Column }) {
Row() {
Text('TimeOut')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#ff100f0f')
.height(30)
.width(220)
.margin({ left: 4 })
}
.height(32)
.width(124)
.margin({ top: 32, left: 12 })

Divider().color('gray').strokeWidth(1)
Text(this.showToken)
.fontColor(Color.Blue)
.width('80%')
.margin({ left: '10%'})
Flex({ direction: FlexDirection.Column,
justifyContent: FlexAlign.SpaceBetween }) {
Text("connectMs:")
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#ff100f0f')
.height(40)
.width(220)
.margin({ left: 8 })
TextArea({
text:String(this.timeout.connectMs)
})
.onChange((value) => {
this.timeout.connectMs = Number(value)
})
.width('80%')
.height(40)
.margin({ left: '10%' })
.backgroundColor('0x317aff')
.fontColor('#000')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.borderRadius(10)

Text("transferMs:")
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#ff100f0f')
.height(40)
.width(220)
.margin({ left: 8 })
TextArea({
text:String(this.timeout.transferMs)
})
.onChange((value) => {
this.timeout.transferMs = Number(value)
})
.width('80%')
.height(40)
.margin({ left: '10%'})
.backgroundColor('0x317aff')
.fontColor('#000')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.borderRadius(10)

Button('timeout-test')
.width('80%')
.height(40)
.margin({ left: '10%' })
.backgroundColor('#0A59F7')
.fontColor('#ffffffff')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.borderRadius(20)
.onClick(this.getTimeoutSyn)

Text("结果")
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#000')
.height(30)
.width(220)
.margin({ left: 8 })
TextArea({
text: this.content
})
.width('80%')
.height(260)
.margin({ left: '10%',top: '10%' })
.backgroundColor('0x317aff')
.fontColor('#ffffffff')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.borderRadius(10)
}
.height(644)
.margin({ top: 16 })
}
.linearGradient({
direction: GradientDirection.Top,
repeating: true,
colors: [['#FFFFFF', 0], ['#FFFFFF', 1]]
})
.height('100%')
.width('100%')
}

private getTimeoutSyn = async () => {
const session = rcp.createSession();
const request = new rcp.Request(kHttpServerAddress);
request.configuration = {
transfer: {
timeout: this.timeout
}
}
try {
const response = await session.fetch(request);
console.info(`timeout--success: ${response}`)
this.content=`timeout--success + ${response}`
} catch (err) {
console.error(`timeout--err: ${JSON.stringify(err)}}`)
this.content=`timeout--err + ${JSON.stringify(err)}`
}
session.close();
}

private openSysSwitch = async () => {
let context = getContext(this) as common.UIAbilityContext;
await notificationManager.requestEnableNotification(context);
console.info('testTag', 'System notification switch is open');
}
}

5.2 代码逐步解析

这个页面的 UI 比前两个更丰富一些,因为它需要让用户能够动态输入超时时间。我们来重点看几个关键部分。

1. 超时状态变量

@State timeout:rcp.Timeout = {
connectMs: 1000,
transferMs: 1000
}

这里我们定义了一个 rcp.Timeout 类型的状态变量,初始值是连接超时 1000 毫秒(1秒)、传输超时 1000 毫秒(1秒)。因为是 @State 变量,所以当它的值发生变化时,UI 会自动更新。

2. 可编辑的超时输入框

页面里有两个 TextArea,分别用来输入 connectMstransferMs 的值:

TextArea({
text:String(this.timeout.connectMs)
})
.onChange((value) => {
this.timeout.connectMs = Number(value)
})

这里的逻辑是:TextArea 显示当前的 connectMs 值,当用户修改了文本内容时,通过 onChange 回调将新值转换成数字并更新到状态变量中。这样用户就可以在页面上实时调整超时时间了。

这是一个很巧妙的设计 – 你可以把超时时间设得很小(比如 1 毫秒),然后发请求,这样几乎一定会超时,你就能看到超时错误的输出是什么样的。然后你再把时间调大,请求就又能成功了。这个交互式的体验比纯看文档直观多了。

3. 使用 rcp.Request 和 session.fetch 发请求

注意!这个页面的请求方式和前面两个页面不太一样:

const session = rcp.createSession();
const request = new rcp.Request(kHttpServerAddress);
request.configuration = {
transfer: {
timeout: this.timeout
}
}
try {
const response = await session.fetch(request);

之前我们用的是 session.get(url) 这种便捷方法,但这里用了更底层的方式:

  1. rcp.createSession() – 创建会话(没有配置 baseAddress)
  2. new rcp.Request(kHttpServerAddress) – 手动创建一个 Request 对象,传入完整的请求地址
  3. request.configuration = { transfer: { timeout: this.timeout } } – 给请求配置超时参数。注意这里超时是放在 configuration.transfer.timeout 里面的
  4. session.fetch(request) – 用 fetch 方法发送请求

session.fetch()session.get() 更灵活,因为它接受一个完整的 Request 对象,你可以在 Request 上配置各种参数(超时、请求头、请求体等等)。

4. 记得关闭 session

session.close();

在 try-catch 之后,我们调用了 session.close()。这是一个好习惯!虽然 JavaScript 有垃圾回收机制,但显式地关闭 session 可以更快地释放底层资源。特别是在频繁创建 session 的场景下,不关闭可能会导致资源泄漏。

小提示:前面几个页面里没有调用 session.close(),严格来说是不太规范的。在实际项目中建议都加上。


六、断点续传页面(TransferRange)-- 大文件下载的救星

最后一个功能点,也是我觉得最实用的一个 – 断点续传

想象你在下载一个 1GB 的文件,下载到 80% 的时候网络断了。如果没有断点续传,你就得从头重新下载。但有了断点续传,你可以从 80% 的位置继续下载,不用浪费已经下载好的那 800MB。

在 HTTP 协议中,断点续传是通过 Range 请求头实现的。远场通信服务帮我们封装好了这个功能,只需要配置 transferRange 就行。

6.1 完整代码

import { rcp } from '@kit.RemoteCommunicationKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { notificationManager } from '@kit.NotificationKit';
import { common } from '@kit.AbilityKit';

const kHttpServerAddress = "https://httpbin.org";
@Entry
@Component
struct  requestContent{
@State showToken: string = '';
@State content: string = '';
@State transferRange:rcp.TransferRange = {
from:10,
to:100
}
async aboutToAppear() {
this.openSysSwitch();
}

build() {
Flex({ direction: FlexDirection.Column }) {
Row() {
Text('TransferRange')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#ff100f0f')
.height(30)
.width(220)
.margin({ left: 4 })
}
.height(32)
.width(124)
.margin({ top: 32, left: 12 })

Divider().color('gray').strokeWidth(1)
Text(this.showToken)
.fontColor(Color.Blue)
.width('80%')
.margin({ left: '10%'})
Flex({ direction: FlexDirection.Column,
justifyContent: FlexAlign.SpaceBetween }) {
Text("from:")
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#ff100f0f')
.height(40)
.width(220)
.margin({ left: 8 })
TextArea({
text:String(this.transferRange.from)
})
.onChange((value) => {
this.transferRange.from = Number(value)
})
.width('80%')
.height(40)
.margin({ left: '10%' })
.backgroundColor('0x317aff')
.fontColor('#000')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.borderRadius(10)

Text("to:")
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#ff100f0f')
.height(40)
.width(220)
.margin({ left: 8 })
TextArea({
text:String(this.transferRange.to)
})
.onChange((value) => {
this.transferRange.to = Number(value)
})
.width('80%')
.height(40)
.margin({ left: '10%'})
.backgroundColor('0x317aff')
.fontColor('#000')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.borderRadius(10)

Button('断点续传')
.width('80%')
.height(40)
.margin({ left: '10%' })
.backgroundColor('#0A59F7')
.fontColor('#ffffffff')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.borderRadius(20)
.onClick(this.getTransferSyn)

Text("结果")
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#000')
.height(30)
.width(220)
.margin({ left: 8 })
TextArea({
text: this.content
})
.width('80%')
.height(260)
.margin({ left: '10%',top: '10%' })
.backgroundColor('0x317aff')
.fontColor('#ffffffff')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.borderRadius(10)
}
.height(644)
.margin({ top: 16 })
}
.linearGradient({
direction: GradientDirection.Top,
repeating: true,
colors: [['#FFFFFF', 0], ['#FFFFFF', 1]]
})
.height('100%')
.width('100%')
}

private getTransferSyn = async () => {
try {
const session = rcp.createSession();
const request = new rcp.Request(kHttpServerAddress,"GET");
request.transferRange = this.transferRange
const resp = await session.fetch(request);
this.content=`${kHttpServerAddress} 断点续传成功`+ resp
console.info(`断点续传成功: ${resp}`)
session.close();
} catch (err) {
let e: BusinessError = err as BusinessError;
this.content=`${kHttpServerAddress} 断点续传失败 ${JSON.stringify(err)}}`
console.error(`断点续传失败: ${JSON.stringify(err)}`)
console.error(`Get push token catch error: ${JSON.stringify(e.code)}, ${JSON.stringify(e.message)}`);
}
}

private openSysSwitch = async () => {
let context = getContext(this) as common.UIAbilityContext;
await notificationManager.requestEnableNotification(context);
console.info('testTag', 'System notification switch is open');
}
}

6.2 代码逐步解析

UI 部分和超时测试页面非常相似,都是两个输入框 + 一个按钮 + 一个结果展示区。区别在于输入的内容从超时时间变成了字节范围。我们重点看核心逻辑。

1. transferRange 状态变量

@State transferRange:rcp.TransferRange = {
from:10,
to:100
}

rcp.TransferRange 类型有两个字段:

  • from – 起始字节位置(包含)
  • to – 结束字节位置(包含)

初始值 from: 10, to: 100 表示我们只请求响应体的第 10 到第 100 个字节。这对于测试来说足够了 – 你可以清楚地看到返回的数据确实只有那么一小段。

2. 页面上的输入框

和超时页面一样,这里也有两个 TextArea:

TextArea({
text:String(this.transferRange.from)
})
.onChange((value) => {
this.transferRange.from = Number(value)
})

用户可以在页面上修改 fromto 的值,然后点击按钮发起带断点续传的请求。

3. 核心:设置 transferRange 并发请求

private getTransferSyn = async () => {
try {
const session = rcp.createSession();
const request = new rcp.Request(kHttpServerAddress,"GET");
request.transferRange = this.transferRange
const resp = await session.fetch(request);
this.content=`${kHttpServerAddress} 断点续传成功`+ resp
console.info(`断点续传成功: ${resp}`)
session.close();
} catch (err) {
let e: BusinessError = err as BusinessError;
this.content=`${kHttpServerAddress} 断点续传失败 ${JSON.stringify(err)}}`
console.error(`断点续传失败: ${JSON.stringify(err)}`)
console.error(`Get push token catch error: ${JSON.stringify(e.code)}, ${JSON.stringify(e.message)}`);
}
}

这段代码的流程是:

  1. rcp.createSession() – 创建会话
  2. new rcp.Request(kHttpServerAddress, "GET") – 创建 Request 对象,指定 URL 和请求方法为 GET
  3. request.transferRange = this.transferRange – 这就是断点续传的关键!把用户设置的 transferRange(from 和 to)赋值给 request 对象。远场通信服务会自动在底层添加 Range: bytes=10-100 这样的请求头
  4. session.fetch(request) – 发送请求
  5. 成功的话显示结果,失败的话捕获错误并显示错误信息
  6. session.close() – 关闭会话,释放资源

注意:这次 session.close() 放在了 try 块里面,成功时才关闭。严格来说,应该用 finally 块来确保无论成功失败都会关闭。不过作为 Demo 这样写也没问题,大家可以自行优化。

4. 错误处理更详细

let e: BusinessError = err as BusinessError;
this.content=`${kHttpServerAddress} 断点续传失败 ${JSON.stringify(err)}}`
console.error(`断点续传失败: ${JSON.stringify(err)}`)
console.error(`Get push token catch error: ${JSON.stringify(e.code)}, ${JSON.stringify(e.message)}`);

在断点续传页面,错误处理输出得更详细了。除了完整的错误 JSON,还单独输出了 e.codee.message。在调试的时候,这两个信息非常关键 – 你可以根据错误码去查文档,快速定位问题。


七、知识点总结

好了,四个页面都过了一遍。现在咱们来把核心知识点梳理一下,方便大家日后查阅。

7.1 远场通信服务的三种请求方式

通过这篇教程,我们其实学到了两种发起请求的方式:

方式 用法 适用场景
session.get(url) 简洁,一行搞定 简单的 GET 请求
session.fetch(request) 需要先构造 Request 对象 需要配置超时、断点续传等高级功能

如果只是简单地 GET 一下,session.get() 最方便。但如果需要配置超时、请求头、请求体等,那就得用 session.fetch() + rcp.Request 了。

7.2 Session 的创建方式

方式 代码 说明
带 baseAddress rcp.createSession({ baseAddress: url }) 适合所有请求都指向同一服务器
不带参数 rcp.createSession() 每次请求都要写完整 URL

7.3 核心配置项

  • baseAddress – 基础 URL,在 createSession 时设置
  • timeout – 超时配置(connectMs + transferMs),在 request.configuration.transfer.timeout 中设置
  • transferRange – 断点续传范围(from + to),直接设置在 request 对象上

7.4 好习惯清单

  1. try-catch 包裹所有网络请求,避免未处理的异常导致应用崩溃
  2. 请求完成后调用 session.close() 释放资源
  3. 使用 hilog 替代 console 做正式的日志输出
  4. 使用 @State 管理请求结果,自动触发 UI 更新

八、常见问题

Q:baseAddress 和请求 URL 拼接的规则是什么?

A:如果请求 URL 是绝对路径(以 http://https:// 开头),则直接使用请求 URL,忽略 baseAddress。如果请求 URL 是相对路径,则会拼接到 baseAddress 后面。

Q:超时时间设置多少合适?

A:这取决于你的业务场景。一般来说,连接超时 5-10 秒、传输超时 30-60 秒是比较常见的设置。对于需要快速响应的接口,可以把时间设短一些;对于上传/下载大文件的操作,需要设得更长。

Q:断点续传需要服务器支持吗?

A:是的!服务器需要支持 HTTP Range 请求才行。httpbin.org 支持 Range 请求,所以我们的测试可以正常工作。在实际项目中,你需要确认你的后端 API 是否支持 Range 请求头。


九、写在最后

远场通信服务(Remote Communication Kit)是 HarmonyOS 中处理网络请求的官方推荐方案。虽然市面上也有一些第三方网络库,但使用官方 Kit 有几个明显的好处:

  1. 与系统能力深度集成 – 比如和通知、后台任务等系统服务的配合更好
  2. 维护有保障 – 跟随系统版本更新,不用担心第三方库停止维护
  3. API 设计统一 – rcp 的 API 风格与现代 Web 标准接近,学起来不难

希望这篇文章能帮你快速上手远场通信服务。如果你在开发过程中遇到什么问题,欢迎在评论区留言讨论,我们一起交流进步。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐