HarmonyOS —— Remote Communication Kit 请求暂停 恢复 & 断点续传实战笔记

HarmonyOS —— Remote Communication Kit 请求暂停 / 恢复 & 断点续传实战笔记

这篇可以当成我给自己留的“网络大文件场景小抄”:
网络慢、需要暂停下载、想实现断点续传,这一节 Remote Communication Kit 都给了现成的能力。


一、能力概览 & 设备支持

鸿蒙开发者第四期活动

Remote Communication Kit 在网络传输这块,不只是“发请求、收响应”这么简单,它还内置了:

  • 请求发送 / 接收的暂停与恢复
  • 基于传输范围(TransferRange)的断点续传

典型使用场景:

  • 上传 / 下载大文件时,用户切到 4G / Wi-Fi,想先暂停;
  • 断网 / 应用被打断后,希望下次从上次进度继续下载,而不是重头来;
  • 做自定义下载器(分块、拼接、支持暂停/继续)。

设备支持和之前几节一样:

  • 支持设备:Phone / 2in1 / Tablet / Wearable
  • 5.1.1(19) 起:新增支持 TV

一句囫囵记:
请求暂停、恢复与断点续传能力支持 Phone / 2in1 / Tablet / Wearable,5.1.1(19) 起支持 TV


二、请求暂停 / 恢复:SendingPausePolicy & PausePolicy

1. 代码里用到的基础工具

import { rcp } from '@kit.RemoteCommunicationKit';
import { util } from '@kit.ArkTS';

const HTTP_SERVER_POST: string = "https://example.org/anything";

// 定义调试信息接口
interface StringifiedDebugInfo {
  type: rcp.DebugEvent;
  data: string;
}

// 调试信息源类型:可能来自 response 或单独的 DebugInfo 数组
type DebugInfoSource = undefined | rcp.DebugInfo[] | rcp.Response;

这里的 Debug 相关逻辑,是用来从调试信息里筛出“暂停发送 / 恢复发送”的事件,方便验证是否真的发生了暂停/恢复。


2. 调试信息序列化:debugInfoStringify

function debugInfoStringify(infoSource: DebugInfoSource): StringifiedDebugInfo[] {
  const debugInfo = Array.isArray(infoSource)
    ? (infoSource as rcp.DebugInfo[])
    : (infoSource as rcp.Response).debugInfo;

  if (!debugInfo) {
    return [];
  }

  const decoder = util.TextDecoder.create('utf-8');
  return debugInfo.map((i: rcp.DebugInfo): StringifiedDebugInfo => {
    return {
      type: i.type,
      data: decoder.decodeToString(new Uint8Array(i.data)).trim(),
    };
  });
}
  • response.debugInfo 里包含了 RCP 的“内部调试事件”;
  • TextDecoder 解出人类可读的字符串,方便我们按前缀匹配。

3. 提取“发送暂停 / 恢复”事件

function getSendPausedEvents(debugInfo: DebugInfoSource) {
  return debugInfoStringify(debugInfo).filter((i) =>
    i.data.startsWith('[[RCP]]: Pause sending')
  );
}

function getSendResumedEvents(debugInfo: DebugInfoSource) {
  return debugInfoStringify(debugInfo).filter((i) =>
    i.data.startsWith('[[RCP]]: Resume sending')
  );
}

这两个函数就是在调试信息里找:

  • [[RCP]]: Pause sending...
  • [[RCP]]: Resume sending...

这样我们就能知道:发送过程到底有没有被暂停 / 恢复过


4. 发送暂停策略:SendingPausePolicy + PausePolicy

核心示例函数:

const SendingPauseByTimeout = async (done: Function): Promise<void> => {
  const session = rcp.createSession();
  const request = new rcp.Request(HTTP_SERVER_POST);

  // 1. 定义发送暂停策略:kind 为 'timeout',timeoutMs 为 1ms
  const sendPolicy: rcp.SendingPausePolicy = {
    kind: 'timeout',
    timeoutMs: 1,
  };

  // 2. 定义总的暂停策略,把发送策略挂上去
  const pausePolicy: rcp.PausePolicy = {
    sending: sendPolicy,
  };

  // 3. 把暂停策略 & 调试跟踪配置挂到 request 上
  request.configuration = {
    transfer: {
      pausePolicy: pausePolicy,
    },
    tracing: {
      infoToCollect: {
        textual: true,
      },
    },
  };

  // 4. 准备请求体数据
  const data = 'TestData';
  request.headers = {
    'Content-Length': data.length.toString(),
  };

  let isReadCompleted = false;
  request.method = 'POST';

  // 5. 自定义请求体生成逻辑
  request.content = (maxSize) => {
    if (isReadCompleted) {
      return new ArrayBuffer(0);
    }
    isReadCompleted = true;
    const buffer = new ArrayBuffer(data.length);
    util.TextEncoder.create('utf-8').encodeIntoUint8Array(data, new Uint8Array(buffer));
    return buffer;
  };

  // 6. 发起请求
  const response = await session.fetch(request);

  // 7. 从响应的 debugInfo 里,查看发送暂停 / 恢复事件
  const pausedEvents = getSendPausedEvents(response);
  const resumedEvents = getSendResumedEvents(response);

  console.info(`pausedEvents: ${JSON.stringify(pausedEvents)}`);
  console.info(`resumedEvents: ${JSON.stringify(resumedEvents)}`);

  session.close();
  done();
}

关键点拆解:

  1. SendingPausePolicy

    • kind: 'timeout':表示使用“超时触发暂停”策略;
    • timeoutMs: 1:超时时间非常短(1ms),示例里用来模拟“很容易触发暂停”。
  2. PausePolicy

    • sending 字段设为上面的 sendPolicy,也就是说:

      发送过程遵循发送暂停策略;

    • 文档里提到“接收暂停”同样支持,只是示例展示的是 sending 这一侧。

  3. request.configuration.transfer.pausePolicy

    • 把这一套策略挂上去之后,请求发送过程中就会根据策略自动触发“发送暂停 / 恢复”机制。
  4. tracing.infoToCollect.textual = true

    • 打开文本形式的跟踪信息,方便你在 response.debugInfo 里看到暂停/恢复日志。
  5. 调试验证

    • getSendPausedEvents(response) / getSendResumedEvents(response) 就能检验 RCP 到底有没有按策略暂停/恢复。

这段示例主要是教你:怎么配置暂停策略 + 怎么在 Debug 里看见暂停/恢复事件
真正业务里,你可以把 kind 换成其他策略(比如手动控制),也可以把 timeoutMs 改成合适的值。


三、断点续传:基于 transferRange 的分段下载

1. 基本思想

断点续传本质上就是:

  • 每次只请求文件的一部分(即设置传输范围),例如:
    • 第一次:下载 [0, 100)
    • 第二次:下载 [100, 200)
  • 每段下载完之后,记录本次“下载截止位置”;
  • 下次继续时,从上次截止的位置开始,再请求下一段。

在 Remote Communication Kit 里,这个“传输范围”由 request.transferRange 控制:

request.transferRange = { from: number, to: number };

你可以把它理解为 HTTP Range 头的语义封装。


2. 会话 & 请求初始化

import { rcp } from '@kit.RemoteCommunicationKit';
import { BusinessError } from '@kit.BasicServicesKit';

// 创建会话
let session: rcp.Session | null = rcp.createSession();

// 定义服务器地址
const kHttpServerAddress = "http://www.example.com/fetch";

// 创建请求
const request = new rcp.Request(kHttpServerAddress, "GET");

// 定义变量记录下载文件的大小
let totalSize = 0;

// 定义一个存储上次传输位置的变量
let lastTransferPosition = 0;
  • totalSize:整个文件的总大小(字节数)
  • lastTransferPosition:当前已经成功下载到的位置(相当于断点)

3. 先获取文件总大小:getTotalSize

示例是通过响应头里的 content-range 来拿总大小:

async function getTotalSize(): Promise<number> {
  request.transferRange = { from: 0, to: 1 };
  let totalSize = 0;
  try {
    let rep = await session?.fetch(request);
    if (rep) {
      // 从 header 的 content-range 字段中提取文件大小
      let contentRange = rep.headers['content-range'];
      let sizeStr = contentRange
        ? contentRange.substring(contentRange.indexOf('\/') + 1, contentRange.length)
        : '0';
      totalSize = Number(sizeStr);
    }
  } catch (err: any) {
    console.error(`getTotalSize err: code is ${err.code}, message is ${JSON.stringify(err)}`);
  }
  console.info(`getTotalSize totalSize: ${totalSize.toString()}`);
  return totalSize;
}

典型的 Content-Range 形态类似:

bytes 0-1/1024

这里是简单粗暴地:

  • 找到 / 后面的那一段(例如 1024
  • 转成 Number,当成文件总大小

⚠ 注意:这种做法依赖服务端正确返回 Content-Range 头,实际使用时要做好兜底逻辑(比如换 HEAD 请求、用 Content-Length 等其他方式)。


4. 根据传输范围下载一段:downloadTransfer

function downloadTransfer(from: number, to: number) {
  // 设置请求的数据传输范围
  request.transferRange = { from: from, to: to };

  session?.fetch(request).then((rep) => {
    if (rep.body) {
      // 这里应该把 rep.body 写入本地文件,对应 from ~ to 的内容
      console.info(`Response succeeded: ${JSON.stringify(rep.headers)}`);

      // 下一次传输的起始位置 = 上次的位置 + 本次传输数据的长度
      lastTransferPosition += rep.body.byteLength;

      if (lastTransferPosition < totalSize) {
        // 计算下一次传输范围的结束位置,这里每次传 100 字节为例
        const nextTo = Math.min(lastTransferPosition + 100, totalSize);

        // 递归调用继续下载下一段数据
        downloadTransfer(lastTransferPosition, nextTo);
      } else {
        console.info("Response succeeded, completed.");
      }
    }
  }).catch((err: BusinessError) => {
    console.error(`Continue transfer error: code is ${err.code}, message is ${err.message}`);
  });
}

这里的逻辑可以理解为:

  1. 设置当前这段要下载的范围 [from, to)
  2. session.fetch(request) 拿到这一段数据;
  3. rep.body 写到本地文件对应区域(示例里没写这一步,但真实场景需要);
  4. lastTransferPosition 往前推进 rep.body.byteLength
  5. 如果还没到 totalSize,继续算下一段 [lastTransferPosition, nextTo)
  6. 递归调用直到下载完成。

示例中用了固定 chunk size = 100,可以抽成一个常量,比如 const CHUNK_SIZE = 1024 * 64;


5. 开始下载:startDownload

async function startDownload() {
  if (!session) {
    session = rcp.createSession();
  }
  // 传输位置归零
  lastTransferPosition = 0;
  // 获取要下载文件的总大小
  totalSize = await getTotalSize();
  // 计算第一段的结束位置
  const nextTo = Math.min(lastTransferPosition + 100, totalSize);
  // 开始下载第一段
  downloadTransfer(lastTransferPosition, nextTo);
}
  • 重置 lastTransferPosition
  • 通过 getTotalSize() 拿到总长度;
  • 再从 0 开始下载第一段。

6. 暂停、继续、停止下载

暂停下载

function pauseDownload() {
  // 取消当前下载请求
  session?.cancel(request);
}
  • session.cancel(request):取消当前这个请求;
  • 由于我们每段是一次 fetch,暂停时就会中断当前这一次分段请求;
  • 但已经下载成功的部分我们通过 lastTransferPosition 记录下来了。

继续下载

function resumeDownload() {
  // 计算下一段的结束位置
  const nextTo = Math.min(lastTransferPosition + 100, totalSize);
  // 从 lastTransferPosition 继续下载下一段
  downloadTransfer(lastTransferPosition, nextTo);
}
  • 关键是:不重头来,而是从上一次 lastTransferPosition 继续。

停止下载

function stopDownload() {
  // 取消当前请求
  session?.cancel(request);
  // 关闭 session
  session?.close();
  session = null;
}
  • 停止和暂停的区别在于:
    • 暂停:只取消当前请求,但保留 session & 状态,可以 resume;
    • 停止:直接 cancel + close,后续想下载需要重新建 session / request。

四、这一节的实战总结(给自己的一句话提示)

  • 暂停 / 恢复:
    • PausePolicy 控制发送 / 接收;
    • 发送端示例:SendingPausePolicy(kind: 'timeout', timeoutMs: ...)
    • 配合 tracing.infoToCollect.textual + response.debugInfo 可以检查暂停 / 恢复事件。
  • 断点续传:
    • 核心是 request.transferRange = { from, to }
    • 先通过 Content-Range 拿总大小;
    • 再用 lastTransferPosition 滚动推进,分块下载;
    • pause = cancel(request)resume = 从 lastTransferPosition 继续拉下一块。
  • 设备支持:
    • Phone / 2in1 / Tablet / Wearable
    • 5.1.1(19) 起新增 TV

如果你接下来想把:

posted @ 2025-12-13 20:23  遇到困难睡大觉哈哈  阅读(3)  评论(0)    收藏  举报