实现请求暂停、恢复与断点续传
约束与限制
请求暂停、恢复与断点续传能力支持Phone、2in1、Tablet、Wearable设备。并且从5.1.1(19)开始,新增支持TV设备。
请求暂停、恢复
场景介绍
Remote Communication Kit提供完善的功能支持,包括请求的暂停和恢复功能。这不仅涵盖接收暂停,还包括发送暂停。
使用实例
-
导入需要的模块。
import { rcp } from '@kit.RemoteCommunicationKit';import { util } from '@kit.ArkTS'; -
定义调试信息接口、调试信息源类型以及调试信息序列化函数,用于将调试信息序列化为StringifiedDebugInfo数组。函数首先根据infoSource的类型获取调试信息,然后使用TextDecoder将调试信息的data字段解码为字符串,并返回一个包含解码后的调试信息的数组。
const HTTP_SERVER_POST: string = "https://example.org/anything";// 定义调试信息接口interface StringifiedDebugInfo {type: rcp.DebugEvent;data: string;};// 定义调试信息源类型type DebugInfoSource = undefined | rcp.DebugInfo[] | rcp.Response;// 定义调试信息序列化函数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(),};});} -
获取发送暂停和恢复事件,用于从调试信息中筛选出发送暂停和恢复事件。
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'));} -
编写发起请求的函数。
const SendingPauseByTimeout = async (done: Function): Promise<void> => {const session = rcp.createSession();const request = new rcp.Request(HTTP_SERVER_POST);// 定义发送暂停策略,kind为'timeout',timeoutMs为1msconst sendPolicy: rcp.SendingPausePolicy = {kind: 'timeout',timeoutMs: 1,};// 定义暂停策略,sending字段引用了上述定义的发送暂停策略const pausePolicy: rcp.PausePolicy = {sending: sendPolicy,};// 设置请求的配置,包括传输策略和跟踪信息request.configuration = {transfer: {pausePolicy: pausePolicy,},tracing: {infoToCollect: {textual: true,},},};// 定义请求体数据const data = 'TestData';// 设置请求头,'Content-Length'字段表示请求体的长度request.headers = {'Content-Length': data.length.toString(),};// 定义布尔型标志变量用于控制请求体生成let isReadCompleted = false;// 设置请求方法为POSTrequest.method = 'POST';// 定义请求体内容生成函数,如果read为true,则返回空的ArrayBuffer,否则生成包含请求体数据的ArrayBufferrequest.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;};// 发送请求并等待响应const response = await session.fetch(request)// 从响应的调试信息中获取发送暂停和恢复事件const pausedEvents = getSendPausedEvents(response);const resumedEvents = getSendResumedEvents(response);// 关闭会话session.close();// 调用完成回调函数done();}
实现断点续传
场景介绍
在需要接续数据请求的场景中,用户可以通过定义TransferRange对象的from和to属性来控制数据的截取范围。下载的内容可以被准确地截取并拼接到目标文件中,确保数据的完整性和一致性,开发者可以灵活地管理和恢复下载过程。
使用示例
-
导入模块。
import { rcp } from '@kit.RemoteCommunicationKit';import { BusinessError } from '@kit.BasicServicesKit'; -
创建session,定义请求URL,并对request进行配置,同时定义变量以记录下载文件的总大小。
// 创建会话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; -
编写一个函数以获取要下载的文件大小。有多种方法可以获取下载文件的大小,请根据实际需求选择合适的方法。在本例中,通过从响应数据的header中的content-range字段来获取下载文件的总大小。
/*** 获取要下载文件的大小** @returns 文件的大小*/async function getTotalSize(): Promise<number> {request.transferRange = { from: 0, to: 1 };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) {console.error(`getTotalSize error code is ${err.code}, error data is ${err.data}`);}console.info(`getTotalSize totalSize: ${totalSize.toString()}`);return totalSize;} -
编写一个依据传输范围下载文件的函数。
/*** 根据传输范围下载文件** @param from - 传输范围的起始位置* @param to - 传输范围的结束位置*/function downloadTransfer(from: number, to: number) {// 设置请求的数据传输范围request.transferRange = { from: from, to: to };session?.fetch(request).then((rep) => {if (rep.body) {// 处理响应,可以在此处将文件保存到本地console.info(`Response succeeded: ${JSON.stringify(rep.headers)}`);// 下次传输的起始位置 = 上次的位置 + 本次传输数据的长度lastTransferPosition += rep.body.byteLength;if (lastTransferPosition < totalSize) {// 计算下一次传输范围的结束位置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}`);});} -
使用以下方式开始下载。
/*** 开始下载*/async function startDownload() {if (!session) {session = rcp.createSession();}// 传输位置归零lastTransferPosition = 0;// 获取要下载文件的总大小totalSize = await getTotalSize();// 计算传输范围的结束位置const nextTo = Math.min(lastTransferPosition + 100, totalSize);// 开始下载downloadTransfer(lastTransferPosition, nextTo);} -
使用以下方式暂停下载。
/*** 暂停下载*/function pauseDownload() {// 取消下载请求session?.cancel(request);} -
使用以下方式继续下载。
/*** 继续下载*/function resumeDownload() {// 计算传输范围的结束位置const nextTo = Math.min(lastTransferPosition + 100, totalSize);// 开始下载downloadTransfer(lastTransferPosition, nextTo);} -
使用以下方式停止下载。
/*** 停止下载*/function stopDownload() {// 取消下载请求session?.cancel(request);// 关闭sessionsession?.close();session = null;}