HarmonyOS远场通信服务HTTP请求开发实录
好了,四个页面都过了一遍。现在咱们来把核心知识点梳理一下,方便大家日后查阅。远场通信服务(Remote Communication Kit)是 HarmonyOS 中处理网络请求的官方推荐方案。与系统能力深度集成– 比如和通知、后台任务等系统服务的配合更好维护有保障– 跟随系统版本更新,不用担心第三方库停止维护API 设计统一– rcp 的 API 风格与现代 Web 标准接近,学起来不难希望这篇
嗨,朋友们!今天咱们来聊一个在 HarmonyOS 开发中超级实用的东西 – 远场通信服务(Remote Communication Kit)。不管你是想从服务器拉数据、提交表单,还是做断点续传下载大文件,这套东西都能帮你搞定。
说实话,我刚接触 HarmonyOS NEXT 开发的时候,最头疼的就是网络请求。之前在 Android 里用 OkHttp,在 Web 里用 fetch/axios,到了鸿蒙这一看,怎么发个 HTTP 请求都不太一样了?后来研究了华为官方的远场通信服务,才发现原来事情可以这么简单。
所以今天这篇文章,我就手把手带着大家,把远场通信服务的核心功能全部过一遍。看完这篇,你就能在自己的鸿蒙应用里自如地发起网络请求了。
咱们要覆盖的内容包括:
- 基础 URL 配置 – 配一个基础地址,后续请求直接用相对路径
- 连接超时和传输超时 – 别让请求一直挂着等不到结果
- 断点续传 – 大文件下载断了一半?没关系,接上继续
准备好了吗?那我们开始吧!
一、远场通信服务是个啥?
在正式写代码之前,咱们先花一分钟搞清楚远场通信服务到底是个什么东西。
官方的说法是这样的:
远场通信服务(Remote Communication Kit)是华为提供的 HTTP 发起数据请求的 NAPI 封装。应用通过远场通信服务可便捷快速地向服务器发起数据请求。
翻译成人话就是:这是一套帮你发 HTTP 请求的工具库。你不需要自己去处理底层的 socket 连接、协议解析这些脏活累活,调用几个 API 就行了。
核心使用方式就三步:
rcp.createSession()– 创建一个会话(session)session.get()/session.fetch()– 发请求- 处理返回结果
对,就这么简单。咱们往下看具体的代码。
二、整体架构 – 四个页面都在干什么?
在本次教程中,我们一共会创建 四个页面,每个页面负责演示远场通信服务的一个功能点:
| 页面 | 文件名 | 功能说明 |
|---|---|---|
| 主界面 | 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.AbilityKit的common– 获取 UIAbility 上下文用的@kit.RemoteCommunicationKit的rcp– 这个就是远场通信服务的核心,所有网络请求相关的 API 都在这里面@kit.BasicServicesKit的BusinessError– 业务错误类型,用来在 catch 里做类型断言@kit.NotificationKit的notificationManager– 通知管理器,用来请求开启系统通知
第二部分:常量和组件定义
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 布局,从上到下依次是:
- 标题文字 “BaseAddress”
- 分割线
- 一个蓝色按钮 “baseAddress-Test”
- “结果” 文字标签
- 一个文本区域(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 方法:
rcp.createSession({ baseAddress: kHttpServerAddress })– 创建一个会话,同时配置了baseAddress为https://httpbin.org。baseAddress是一个基础 URL,后续在会话中发请求时,如果传入的是相对路径,就会自动拼上这个基础地址。session.get("https://www.baidu.com")– 向百度发起 GET 请求,获取响应。- 成功时:将响应结果通过
this.content显示在页面上。 - 失败时:将错误信息捕获并通过
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 支持日志级别、标签、格式化输出等功能,更适合在正式项目中使用。在这个页面里,我们同时用了 hilog 和 console,你可以根据自己的习惯选择。
看这行:
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,分别用来输入 connectMs 和 transferMs 的值:
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) 这种便捷方法,但这里用了更底层的方式:
rcp.createSession()– 创建会话(没有配置 baseAddress)new rcp.Request(kHttpServerAddress)– 手动创建一个 Request 对象,传入完整的请求地址request.configuration = { transfer: { timeout: this.timeout } }– 给请求配置超时参数。注意这里超时是放在configuration.transfer.timeout里面的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)
})
用户可以在页面上修改 from 和 to 的值,然后点击按钮发起带断点续传的请求。
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)}`);
}
}
这段代码的流程是:
rcp.createSession()– 创建会话new rcp.Request(kHttpServerAddress, "GET")– 创建 Request 对象,指定 URL 和请求方法为 GETrequest.transferRange = this.transferRange– 这就是断点续传的关键!把用户设置的transferRange(from 和 to)赋值给 request 对象。远场通信服务会自动在底层添加Range: bytes=10-100这样的请求头session.fetch(request)– 发送请求- 成功的话显示结果,失败的话捕获错误并显示错误信息
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.code 和 e.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 好习惯清单
- 用
try-catch包裹所有网络请求,避免未处理的异常导致应用崩溃 - 请求完成后调用
session.close()释放资源 - 使用
hilog替代console做正式的日志输出 - 使用
@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 有几个明显的好处:
- 与系统能力深度集成 – 比如和通知、后台任务等系统服务的配合更好
- 维护有保障 – 跟随系统版本更新,不用担心第三方库停止维护
- API 设计统一 – rcp 的 API 风格与现代 Web 标准接近,学起来不难
希望这篇文章能帮你快速上手远场通信服务。如果你在开发过程中遇到什么问题,欢迎在评论区留言讨论,我们一起交流进步。
更多推荐


所有评论(0)