HarmonyOS —— Remote Communication Kit 流式传输(Streaming)实战笔记

HarmonyOS —— Remote Communication Kit 流式传输(Streaming)实战笔记

这一节可以直接理解成:
“不用一次性把请求/响应全准备好,边产出边发、边收到边处理”
非常适合:大文件传输、日志/埋点上报、实时数据/直播等场景。


一、流式传输是什么?什么时候用它?

鸿蒙开发者第四期活动

在 Remote Communication Kit 里,HTTP 流式传输(Streaming)允许:

  • 请求端(上传):一边生成数据,一边写入流 → 框架在后台 IO 线程异步发送;
  • 响应端(下载):一边从网络读取,一边写入你指定的缓冲区 / 文件 / 自定义流。

相比“普通一次性请求”的好处:

  • 不用一次性把数据都放到内存里(更节省内存)
  • 能更快看到“首响应”,体验上更“实时”
  • 可以做 实时日志上传、逐行发送、逐 chunk 下载,配合前面讲的断点续传、暂停恢复,能撑起一个比较专业的下载器 / 上传模块。

设备 & 版本限制依旧是熟悉那句话:

同步读写流能力支持 Phone / 2in1 / Tablet / Wearable
5.1.1(19) 起,新增支持 TV


二、基于缓冲区队列的流式传输:NetworkInputQueue / NetworkOutputQueue

这一套可以简单理解为:

  • NetworkInputQueue同步写队列 —— 我负责往里丢数据,RCP 帮我按时发出去;
  • NetworkOutputQueue同步读队列 —— 服务器回来的数据,先缓存在队列里,按需读。

1. 同步写:NetworkInputQueue → 流式上传

示例:

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

export const testNetworkInputQueue = () => {
  // 创建同步写队列对象
  const networkInputQueue = new rcp.NetworkInputQueue();

  // 模拟文件通过同步读写流上传场景,将数据写入队列
  let counter = 0;
  const interval = setInterval(() => {
    // 每秒往队列里写一段数据
    networkInputQueue.write('a counter ' + counter++);
    console.info(`networkInputQueue write`);
    if (counter === 10) {
      clearInterval(interval);
      // 写完记得关闭队列,告诉 RCP 不会再有新数据
      networkInputQueue.close();
    }
  }, 1000);

  try {
    // 创建 session
    const session = rcp.createSession();
    console.info(`Post start.`);

    // 发起 POST 请求,同时把 networkInputQueue 作为请求体
    session.post('https://httpbin.org/anything', networkInputQueue).then((response) => {
      console.info(`Response status code is: ${response.statusCode}`);
      if (response && response.statusCode === 200) {
        console.info(`Post succeeded! response: ${response.toString()}`);
      } else {
        console.error(`Post failed.`);
      }
      session.close();
    }).catch((error: Error) => {
      console.error(`Post error: ${JSON.stringify(error)}`);
      session.close();
    });
  } catch (error) {
    console.error(`create session error: ${JSON.stringify(error)}`);
  }
}

这一段在干什么:

  • 先创建一个 NetworkInputQueue
    • 它是“可写队列”,你可以随时 write() 一段数据进去;
    • RCP 的 IO 线程会在合适的时机把队列里的数据 打包发到网络上
  • setInterval 每秒往里写一段字符串(模拟“流式产生数据”的场景,比如日志流、实时采集数据)。
  • 写完 10 次之后,调用 networkInputQueue.close() 告诉框架:后面没有更多数据了
  • session.post(url, networkInputQueue)
    • 请求体直接来自这个队列;
    • 队列写入和网络发送可以并行进行,无需等数据全部准备完。

适合场景:

  • 实时日志 / 埋点上传
  • 边编码边上传(例如视频编码 + 上传合一)

2. 同步读:NetworkOutputQueue → 流式下载

示例:

export const testNetworkOutputQueue = () => {
  // 创建同步读队列对象
  const networkOutputQueue = new rcp.NetworkOutputQueue();

  try {
    const session = rcp.createSession();

    // 配置要拉取的数据大小
    const numOfChunks = 10;
    const chunkLength = 1000;
    const totalBytes = numOfChunks * chunkLength;

    // 发起 GET 请求,响应数据会暂存在 networkOutputQueue 中
    session.get('https://httpbin.org/bytes/' + totalBytes.toString(), networkOutputQueue)
      .then((response) => {
        if (response && response.statusCode === 200) {
          console.info(`get byts succeeded.`);
        } else {
          console.error(`get byts failed.`);
        }
        session.close();
      }).catch((err: Error) => {
      console.error(`get byts error: ${err.message}`);
      session.close();
    });

    // 按需从 networkOutputQueue 中循环读取,每秒读 1000 字节
    let totalGetLength = 0;
    const intervalId = setInterval(() => {
      const chunk = networkOutputQueue.read(chunkLength);
      totalGetLength += chunk.byteLength;
      console.info(`get byts totalGetLength: ${totalGetLength}`);

      if (totalGetLength === totalBytes) {
        clearInterval(intervalId);
        console.info(`get byts finished.`);
      }
    }, 1000);
  } catch (error) {
    console.error(`create session error: ${JSON.stringify(error)}`);
  }
}

关键点:

  • NetworkOutputQueue 充当“缓冲池”:
    • 网络层把响应 body 持续写入队列;
    • 你可以在任意时刻调用 read(chunkLength) 拿一块数据。
  • 示例里每秒读 1000 字节,模拟“按需消费”的业务,比如:
    • 分段写入文件;
    • 逐步解码音视频数据;
    • 流式处理 JSON / 日志 / 二进制协议。

小提示:真实用法里,read() 之后要把数据写入本地文件或交给上层处理,这里只是打印长度示意。


三、基于回调接口的流式传输:uploadFromStream / downloadToStream

上面是“队列模型”——由框架提供 Queue,应用从 Queue 写/读。
下面这一组是“回调接口模型”——你自己实现 ReadStream / WriteStream 接口,RCP 在传输过程中回调你的读/写方法。

1. 上传:实现 ReadStream + uploadFromStream

1)定义 FdReadStream:从文件读数据

import { rcp } from '@kit.RemoteCommunicationKit';
import fs from '@ohos.file.fs';

class FdReadStream implements rcp.ReadStream {
  readonly fd: number;

  constructor(fd: number) {
    this.fd = fd;
  }

  async read(buffer: ArrayBuffer): Promise<number> {
    return fs.read(this.fd, buffer);
  }
}
  • 实现 rcp.ReadStream 接口:
    • 持有一个文件描述符 fd
    • read(buffer) 里调用 fs.read 读数据到 buffer 里;
    • 返回实际读取的字节数。

2)调用 uploadFromStream 上传

export function testUploadFromStream(uploadFilePath: string) {
  try {
    const session = rcp.createSession();
    // 打开需要上传的本地文件
    const file = fs.openSync(uploadFilePath, fs.OpenMode.READ_ONLY);

    // 用 FdReadStream 包装成 RCP 需要的流
    const fileStream = new rcp.UploadFromStream(new FdReadStream(file.fd));

    session.uploadFromStream('https://httpbin.org/anything', fileStream)
      .then((resp) => {
        console.info(`testUploadFromStream response: ${JSON.stringify(resp)}`);
        if (resp && resp.statusCode === 200) {
          console.info(`testUploadFromStream succeeded.`);
        } else {
          console.error(`testUploadFromStream failed.`);
        }
        fs.closeSync(file.fd);
        session.close();
      })
      .catch((error: Error) => {
        console.error(`testUploadFromStream error: ${JSON.stringify(error)}`);
        fs.closeSync(file.fd);
        session.close();
      });
  } catch (error) {
    console.error(`testUploadFromStream error: ${JSON.stringify(error)}`);
  }
}
  • 你只要负责:
    • 打开文件;
    • 实现“怎么从文件读到 buffer”;
  • RCP 会在上传过程中不断回调 read(),把数据流式上传出去。

相比整文件读到内存再上传,流式方式更省内存、适合大文件


2. 下载:实现 WriteStream + downloadToStream

1)定义 FdWriteStream:写数据到文件

class FdWriteStream implements rcp.WriteStream {
  readonly fd: number;

  constructor(fd: number) {
    this.fd = fd;
  }

  async write(buffer: ArrayBuffer): Promise<number | void> {
    return fs.write(this.fd, buffer);
  }
}
  • 实现 rcp.WriteStream 接口:
    • 持有文件描述符,调用 fs.write 把每一块数据写入到文件。

2)调用 downloadToStream 把响应体写到流

export function testDownloadToStream(downloadToPath: string) {
  try {
    const session = rcp.createSession();
    // 打开用于保存下载结果的文件(不存在则创建)
    const file = fs.openSync(downloadToPath, fs.OpenMode.CREATE | fs.OpenMode.WRITE_ONLY);

    // 包装成 DownloadToStream
    const fileStream = {
      kind: 'stream',
      stream: new FdWriteStream(file.fd)
    } as rcp.DownloadToStream;

    session.downloadToStream('https://httpbin.org/bytes/', fileStream)
      .then((resp) => {
        console.info(`testDownloadToStream response: ${JSON.stringify(resp)}`);
        if (resp && resp.statusCode === 200) {
          console.info(`testDownloadToStream succeeded.`);
        } else {
          console.error(`testDownloadToStream failed.`);
        }
        fs.close(file.fd);
        session.close();
      })
      .catch((error: Error) => {
        console.error(`testDownloadToStream error: ${JSON.stringify(error)}`);
        fs.close(file.fd);
        session.close();
      });
  } catch (err) {
    console.error(`testDownloadToStream error: ${JSON.stringify(err)}`);
  }
}
  • 每次网络层收一块数据,就会调用 write(buffer)
  • 你负责把这块数据写到文件中;
  • 最终在 downloadToPath 这个文件里就有完整的内容。

这个模式非常适合配合之前的 断点续传
每一块写入文件指定偏移,再控制 transferRange 去续传。


四、这一节的“考点小抄”

可以直接贴进你的 HarmonyOS 笔记里:

  • 流式传输适用场景
    • 大文件上传下载
    • 实时日志 / 埋点
    • 直播、实时数据流
  • 两类玩法
    1. 基于缓冲区队列
      • NetworkInputQueue:同步写队列 → write() 写数据,close() 结束;
        • 搭配 session.post(url, queue) 做流式上传。
      • NetworkOutputQueue:同步读队列 → read(size) 读数据;
        • 搭配 session.get(url, queue) 做流式下载。
    2. 基于回调接口
      • uploadFromStream(url, UploadFromStream)
        • 实现 ReadStream.read(buffer),从文件/内存/自定义源读数据;
      • downloadToStream(url, DownloadToStream)
        • 实现 WriteStream.write(buffer),把数据写到文件/内存/自定义 sink。
  • 设备支持
    • Phone / 2in1 / Tablet / Wearable
    • 自 5.1.1(19) 起新增 TV 支持
posted @ 2025-12-13 20:23  遇到困难睡大觉哈哈  阅读(2)  评论(0)    收藏  举报