做跨进程数据传输时,很多新手的第一反应是用 Intent 传、用普通 IPC 消息发,但遇到百 MB 级的安装包、视频、高清大图时立刻碰壁:要么直接抛出「事务数据过大」异常,要么传输卡顿、内存飙升、CPU 占用拉满。
在这里插入图片描述

其实鸿蒙系统原生提供了共享内存(SharedMemory) 机制,专门解决跨进程超大文件/大数据的传输痛点:同一块物理内存分别映射到两个进程的虚拟地址空间,数据本身零拷贝,只需要传递一个几字节的内存句柄,传输性能比普通 Binder IPC 高出一个数量级。

本文基于鸿蒙 7(API 26)@kit.CoreIpcKit 标准套件,从零实现百 MB 级文件跨进程共享传输,从原理、完整实现到性能优化全覆盖,附服务端 + 客户端可运行代码,吃透后轻松应对大文件、大图、音视频流等跨进程大数据场景。

一、为什么普通 IPC 搞不定大文件?

普通 Binder 类的消息型 IPC,在大数据场景下有三个天生短板:

  1. 大小硬限制:Binder 事务缓冲区默认仅 1MB 左右,超过阈值直接崩溃,大文件必须拆分多次传输,逻辑复杂且易出错
  2. 两次内存拷贝:数据需要经历「发送方用户空间 → 内核空间 → 接收方用户空间」两次全量拷贝,文件越大 CPU 和内存开销越高
  3. 上下文切换开销:大文件拆分多次发送,进程上下文切换频繁,传输耗时呈线性增长

而共享内存是操作系统级的 IPC 机制,完美规避了以上问题:

  • 服务端申请一块匿名共享物理内存
  • 将这块内存分别映射到两个进程的虚拟地址空间
  • 双方操作的是同一块物理内存,数据本身不需要任何拷贝
  • 跨进程只需要传递一个内存句柄(几字节),传输开销几乎为固定值,文件越大性能优势越明显

简单类比:普通 IPC 像把文件复印一份搬到另一个房间;共享内存像两个房间共用一扇落地窗,两边都能直接读写同一份文件,全程不用复印。

二、前置准备与核心 API

2.1 基础信息

  • 所属套件@kit.CoreIpcKit(核心 IPC 套件,包含共享内存、Binder 远程通信、消息序列化等能力)
  • 最低版本:API 12,API 26 性能与稳定性最优
  • 权限要求:匿名共享内存无需特殊权限,普通应用即可直接使用
  • 适用场景:跨进程大文件传输、高清大图共享、音视频流分发、跨进程大数据计算

2.2 核心 API 速览

API 作用
sharedMemory.create(name, size) 创建指定大小的匿名共享内存,返回内存对象
sharedMemory.map(fd, size) 将共享内存映射到当前进程虚拟空间,返回可读写的 ArrayBuffer
sharedMemory.unmap(buffer) 解除内存映射,释放本地虚拟地址空间
sharedMemory.close(shm) 销毁共享内存对象,释放物理内存
MessageParcel.writeSharedMemory() 将共享内存句柄写入 IPC 消息包,实现跨进程传递
MessageParcel.readSharedMemory() 从 IPC 消息中读取共享内存句柄

三、完整实战:百 MB 文件跨进程零拷贝传输

3.1 整体架构

我们实现典型的「服务端 + 客户端」双进程架构:

  • 服务端 App:提供远程服务,读取本地大文件写入共享内存,通过 IPC 对外暴露内存句柄
  • 客户端 App:绑定远程服务,获取共享内存句柄后直接映射读取,无需拷贝全量数据
  • 控制信令走 Binder IPC,大数据走共享内存,各司其职性能最优

3.2 服务端实现

服务端基于 ServiceExtensionAbility 实现跨进程远程服务,负责文件读取、共享内存创建与数据写入。

第一步:声明服务

module.json5 中注册远程服务:

"extensionAbilities": [
  {
    "name": "FileShareService",
    "srcEntry": "./ets/service/FileShareService.ets",
    "type": "service",
    "exported": true,
    "description": "$string:file_share_service_desc"
  }
]
第二步:服务核心逻辑

在这里插入图片描述

新建 entry/src/main/ets/service/FileShareService.ets

import { ServiceExtensionAbility, Want } from '@kit.AbilityKit';
import { sharedMemory, RemoteObject, MessageParcel, MessageOption } from '@kit.CoreIpcKit';
import { fs } from '@kit.CoreFileKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

const TAG = 'FileShareService';
const SHARE_SERVICE_CODE = 1001; // 业务请求码

export default class FileShareService extends ServiceExtensionAbility {
  private fileShareStub: FileShareStub | null = null;

  onCreate(): void {
    hilog.info(0x0000, TAG, '共享文件服务已启动');
    this.fileShareStub = new FileShareStub(this.context);
  }

  onConnect(want: Want): RemoteObject {
    hilog.info(0x0000, TAG, '客户端已连接');
    return this.fileShareStub!;
  }

  onDisconnect(want: Want): void {
    hilog.info(0x0000, TAG, '客户端已断开');
  }

  onDestroy(): void {
    this.fileShareStub?.release();
    hilog.info(0x0000, TAG, '共享文件服务已销毁');
  }
}

/**
 * 远程服务桩实现
 */
class FileShareStub extends RemoteObject {
  private context: Context;
  private sharedMem: sharedMemory.SharedMemory | null = null;
  private mappedBuffer: ArrayBuffer | null = null;

  constructor(context: Context) {
    super('FileShareStub');
    this.context = context;
  }

  onRemoteRequest(
    code: number,
    data: MessageParcel,
    reply: MessageParcel,
    options: MessageOption
  ): boolean {
    if (code !== SHARE_SERVICE_CODE) return false;

    try {
      // 读取客户端传入的文件名
      const fileName = data.readString();
      hilog.info(0x0000, TAG, `收到共享请求: ${fileName}`);

      // 1. 读取本地文件
      const filePath = `${this.context.filesDir}/${fileName}`;
      const fileStat = fs.stat(filePath);
      const fileSize = fileStat.size;

      // 2. 创建对应大小的共享内存(按4KB页对齐)
      const pageSize = 4096;
      const alignedSize = Math.ceil(fileSize / pageSize) * pageSize;
      this.sharedMem = sharedMemory.create('file_share_mem', alignedSize);

      // 3. 映射到服务端进程空间
      this.mappedBuffer = sharedMemory.map(this.sharedMem.fd, alignedSize);

      // 4. 将文件内容写入共享内存
      const fileContent = fs.read(filePath);
      const uint8View = new Uint8Array(this.mappedBuffer);
      uint8View.set(new Uint8Array(fileContent.buffer));

      // 5. 将共享内存句柄写入回复消息
      reply.writeInt(fileSize);
      reply.writeSharedMemory(this.sharedMem);

      hilog.info(0x0000, TAG, `共享内存准备完成,大小: ${fileSize}字节`);
      return true;
    } catch (err) {
      hilog.error(0x0000, TAG, `处理失败: ${JSON.stringify(err)}`);
      return false;
    }
  }

  /**
   * 释放资源
   */
  release(): void {
    if (this.mappedBuffer) {
      sharedMemory.unmap(this.mappedBuffer);
      this.mappedBuffer = null;
    }
    if (this.sharedMem) {
      sharedMemory.close(this.sharedMem);
      this.sharedMem = null;
    }
  }
}

3.3 客户端实现

客户端负责绑定远程服务、获取共享内存句柄、映射读取数据,全程不拷贝全量文件内容。
在这里插入图片描述
在这里插入图片描述

新建客户端页面 entry/src/main/ets/pages/ClientPage.ets

import { wantAgent } from '@kit.AbilityKit';
import { sharedMemory, RemoteObject, MessageParcel, MessageOption } from '@kit.CoreIpcKit';
import { promptAction } from '@kit.ArkUI';
import { hilog } from '@kit.PerformanceAnalysisKit';

const TAG = 'FileShareClient';
const SHARE_SERVICE_CODE = 1001;

// 服务端配置(替换为实际包名与服务名)
const SERVICE_INFO = {
  bundleName: 'com.example.fileserver',
  abilityName: 'FileShareService',
  deviceId: '' // 同设备留空,跨设备填设备ID
};

@Entry
@Component
struct FileShareClientPage {
  private remoteProxy: RemoteObject | null = null;
  @State resultInfo: string = '';
  @State isConnected: boolean = false;

  /**
   * 绑定远程服务
   */
  async connectService() {
    try {
      const want = {
        bundleName: SERVICE_INFO.bundleName,
        abilityName: SERVICE_INFO.abilityName,
        deviceId: SERVICE_INFO.deviceId
      };

      this.remoteProxy = await wantAgent.connectAbility(want, {
        onConnect: (elementName, proxy) => {
          this.isConnected = true;
          promptAction.showToast({ message: '服务连接成功' });
          hilog.info(0x0000, TAG, '远程服务已连接');
        },
        onDisconnect: () => {
          this.isConnected = false;
          this.remoteProxy = null;
          hilog.info(0x0000, TAG, '远程服务已断开');
        },
        onFailed: () => {
          promptAction.showToast({ message: '服务连接失败' });
        }
      });
    } catch (err) {
      hilog.error(0x0000, TAG, `连接失败: ${JSON.stringify(err)}`);
    }
  }

  /**
   * 通过共享内存读取远程文件
   */
  async readRemoteFile(fileName: string) {
    if (!this.remoteProxy) {
      promptAction.showToast({ message: '请先连接服务' });
      return;
    }

    const startTime = Date.now();
    let mappedBuffer: ArrayBuffer | null = null;
    let shm: sharedMemory.SharedMemory | null = null;

    try {
      // 1. 构造请求数据
      const data = MessageParcel.obtain();
      const reply = MessageParcel.obtain();
      const option = new MessageOption(MessageOption.TF_SYNC);

      data.writeString(fileName);

      // 2. 发起远程调用,获取共享内存句柄
      const success = this.remoteProxy.sendMessageRequest(SHARE_SERVICE_CODE, data, reply, option);
      if (!success) throw new Error('远程调用失败');

      // 3. 读取文件大小与共享内存
      const fileSize = reply.readInt();
      shm = reply.readSharedMemory();

      // 4. 映射到客户端进程空间(零拷贝,直接访问物理内存)
      mappedBuffer = sharedMemory.map(shm.fd, shm.size);

      // 5. 读取数据(此处仅演示读取,实际可直接写入文件、渲染图片等)
      const contentView = new Uint8Array(mappedBuffer, 0, fileSize);
      const costTime = Date.now() - startTime;

      this.resultInfo = `读取成功\n文件大小: ${(fileSize / 1024 / 1024).toFixed(2)}MB\n耗时: ${costTime}ms\n读取字节数: ${contentView.length}`;
      hilog.info(0x0000, TAG, `文件读取完成,耗时${costTime}ms`);

      data.reclaim();
      reply.reclaim();
    } catch (err) {
      hilog.error(0x0000, TAG, `读取失败: ${JSON.stringify(err)}`);
      promptAction.showToast({ message: '读取文件失败' });
    } finally {
      // 6. 释放本地映射(不销毁共享内存本身,服务端管理生命周期)
      if (mappedBuffer) sharedMemory.unmap(mappedBuffer);
    }
  }

  build() {
    Column({ space: 20 }) {
      Text('共享内存客户端')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .width('90%')
        .margin({ top: 40 });

      Button(this.isConnected ? '已连接服务' : '连接远程服务')
        .width('90%')
        .height(50)
        .borderRadius(25)
        .backgroundColor(this.isConnected ? '#00B578' : '#0A59F7')
        .enabled(!this.isConnected)
        .onClick(() => this.connectService());

      Button('读取远程测试文件')
        .width('90%')
        .height(50)
        .borderRadius(25)
        .backgroundColor('#9B59B6')
        .enabled(this.isConnected)
        .onClick(() => this.readRemoteFile('test_large_file.bin'));

      if (this.resultInfo) {
        Text(this.resultInfo)
          .width('90%')
          .padding(16)
          .backgroundColor('#F0F7FF')
          .borderRadius(12)
          .fontSize(14)
          .fontFamily('monospace')
          .whiteSpace(WhiteSpace.PreWrap);
      }

      Blank();

      Text('💡 基于共享内存实现零拷贝大文件传输')
        .fontSize(12)
        .fontColor('#AAAAAA')
        .margin({ bottom: 20 });
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F8F9FA');
  }
}

3.4 运行验证步骤

  1. 准备测试文件:在服务端应用沙箱目录放入一个 50~200MB 的测试文件
  2. 安装应用:分别安装服务端和客户端两个应用到同一台设备
  3. 连接服务:打开客户端,点击连接远程服务
  4. 读取测试:点击读取远程文件,查看耗时与结果
  5. 性能对比:可额外实现普通 IPC 分块传输做对照,直观感受共享内存的性能优势

四、性能优化:5 个核心技巧拉满传输效率

1. 内存大小按页对齐

系统内存以页(通常 4KB)为单位管理,申请共享内存时向上对齐到页大小,避免内存碎片,提升读写命中率。

const pageSize = 4096;
const alignedSize = Math.ceil(realSize / pageSize) * pageSize;

2. 超大文件分块流式映射

GB 级文件不要一次性全量映射到内存,容易导致内存峰值过高。采用「分块映射、流式处理」:每次只映射一段,处理完解映射再映射下一段,内存占用稳定可控。

3. 避免冗余拷贝

直接操作映射后的 ArrayBuffer 视图,不要中间转成普通数组再拷贝一次。处理图片、音视频等场景,可直接将共享内存交给系统渲染/解码组件,实现全链路零拷贝。

4. 权限最小化原则

创建共享内存时按需设置读写权限,客户端只需要读就授予只读权限,避免误写导致的数据异常,同时提升安全性。

5. 配合异步 IPC 提升并发

多客户端场景下使用异步 IPC 消息,避免阻塞主线程;同时合理控制共享内存的生命周期,由服务端统一管理创建与销毁,客户端只做映射与解映射。

五、新手高频踩坑避坑指南

1. 直接把大文件塞进 Parcel 传输

  • 典型报错:TransactionTooLargeException 事务过大
  • 解决:Parcel 只传控制信令和内存句柄,数据本身放在共享内存里,这是共享内存的核心原则

2. 只映射不解映射,造成内存泄漏

  • 问题:只调用 map 不调用 unmap,进程退出后虚拟地址空间泄漏,长时间运行会 OOM
  • 解决:严格遵循「谁映射谁释放」原则,在 finally 块中保证解映射执行;服务端在服务销毁时关闭共享内存

3. 多进程同时写导致数据脏读

  • 问题:两个进程同时写入同一块内存,出现数据竞争、内容错乱
  • 解决:优先使用「单写多读」模型;必须多写时,配合共享互斥锁或信号量做同步保护

4. 共享内存对象直接跨进程传递

  • 问题:直接把 SharedMemory 对象通过普通参数传递,对方进程无法访问
  • 解决:必须通过 MessageParcelwriteSharedMemory/readSharedMemory 序列化传递文件描述符,由内核完成跨进程句柄转换

5. 误以为共享内存可以跨设备

  • 说明:共享内存是同设备内的 IPC 机制,仅适用于同一台设备的不同进程之间;跨设备传输请使用分布式软总线 + ShareKit

六、总结

共享内存是鸿蒙跨进程大数据传输的最优解,它的价值在于「用最小的代码改动,获得最大的性能提升」——不需要改业务逻辑,只需要把数据搬运的通道从 Binder 换成共享内存,就能获得数倍甚至数十倍的性能提升。

对于百 KB 级的小数据,普通 IPC 足够用且更简单;但对于 MB 级甚至 GB 级的大文件、大图、音视频流,共享内存是必选方案。掌握这套机制,再配合合理的内存管理与同步控制,就能轻松应对各种复杂的跨进程大数据场景。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐