HarmonyOS IPC Kit 笔记:我眼里的进程间通信(IPC/RPC)到底在干啥?

鸿蒙第四期活动
这段时间我在把 HarmonyOS 的并发相关内都过一遍:前面看了 TaskPool、Worker、Sendable,这一块是“线程内怎么跑快”;而 IPC Kit 更多是解决“进程之间怎么说话”的问题。正好趁热把我自己理解的内容整理成一篇博客,当作之后写实战代码的铺垫。


1. 先把几个核心名词讲清楚

官方文档里先给了一个小表,其实就是这几个关键词:IPC / RPC / Client / Server / Proxy / Stub。我们直接用自己的话翻译一下。developer.huawei.com+1

  • IPC(Inter Process Communication)——设备内的进程间通信
    同一台设备上,不同进程之间想互相调用接口、传递一点数据,就要走 IPC。比如:一个“后台播放服务进程”和一个“UI 进程”,一个负责播歌,一个负责画界面。
  • RPC(Remote Procedure Call)——设备间的进程间通信
    不同设备之间要协同,就轮到 RPC 出场了。比如:手机上一个进程想远程调用平板、音箱、PC 上的某个服务进程,这就是典型 RPC 场景。底层走的是软总线CSDN+1
  • Client(客户端)
    发起调用的一方。你可以理解为“用户”或者“调用者”。在代码里,它拿到的是一个 Proxy 对象,通过这个 Proxy 去调用远端的接口。
  • Server(服务端)
    真正提供服务的一方。里面有具体业务逻辑,比如“计算、读数据库、访问硬件”等。Server 这边有个对应的 Stub,用来接收 Proxy 传过来的请求并回包。
  • Proxy(代理)
    跑在 Client 进程里,长得和服务端接口一样,但本质是个“壳”。你对 Proxy 调用方法,它会把参数打包成二进制,通过 Binder/软总线发给远端,再把结果拆包给你。
  • Stub(桩)
    跑在 Server 进程里,是和 Proxy 对应的另一端。它负责:
    1. 接收 Proxy 发来的二进制数据;
    2. 反序列化成参数;
    3. 调用真正的业务方法;
    4. 把结果再打包回去给 Proxy。

一句话总结:Client 只和 Proxy 打交道,Server 只和 Stub 打交道,真正跑远程通信的是 Binder 或软总线。博客园+1


2. IPC & RPC 在 HarmonyOS 里的定位

从系统角度看,IPC 和 RPC 的角色其实非常固定:developer.huawei.com+1

  • IPC:Binder 驱动 + 单设备跨进程
    • 跑在同一台设备上;
    • 底层用 Binder 驱动;
    • 适合“后台服务 + 前台 UI”这类经典架构。
  • RPC:软总线驱动 + 跨设备跨进程
    • 跑在不同设备之间;
    • 底层用软总线;
    • 适合多端协同:手机 + 平板 + PC + 智能音箱一起玩。

为什么一定要搞这么麻烦?因为每个进程有自己独立的虚拟内存空间,默认是互相不可见的。你没法直接“拿着另一个进程的指针去读写”,那是内存安全灾难。所以系统提供了一套正规的“过道”——也就是 IPC / RPC 通道——让你规规矩矩地传数据、调方法。


3. IPC Kit 的整体架构:从 Client 到 Server 走一圈

我自己理解的 IPC Kit 结构,可以用一个很土的类比:

SAMgr = 总服务大厅
Server = 窗口办事员
Client = 来办事的市民
Proxy = 你手里的号单
Stub = 办事员那边的受理台

结合官方接口,整个流程基本是这样的:CSDN+1

  1. Server 注册服务(System Ability)
    • Server 进程启动后,把自己的能力注册到 System Ability Manager(SAMgr) 里;
    • 对外暴露一个 ID(systemAbilityId)和一套接口协议。
  2. Client 通过“元能力接口”拿到 Proxy
    • Client 想用某个服务,会先通过“连接服务接口”(本质就是去 SAMgr 查)拿到一个 Proxy
    • 这一步就是你平时写的:构造 Want → 调 connectAbility / 相关连接接口 → 在回调里得到 Proxy。developer.huawei.com+1
  3. Client 调用 Proxy → 底层打包发给 Stub
    • Client 这边正常写:proxy.doSomething(param1, param2)
    • Proxy 会把方法编号 + 参数,打包进一个 MessageParcel,通过 Binder/软总线往远端丢。
  4. Stub 解包 → 调用真实业务 → 回包
    • Server 进程里的 Stub 收到请求;
    • 读出方法 code + 参数;
    • 调用对应的业务实现;
    • 再把结果写进一个回复包发回去。
  5. Client 在 Proxy 侧拿到结果
    • Proxy 收到 reply;
    • 反序列化后返回给 Client 本地代码。

所以从上层开发者视角看,其实就两句:

  • 我这边拿到的是一个“远程对象”;
  • 调它的方法,像本地一样,但实际在对面进程/设备跑。

4. 典型使用场景:什么时候用 IPC,什么时候用 RPC?

4.1 IPC:单设备后台服务

这种场景特别适合 IPC:bbs.songma.com+1

  • 媒体播放服务
    • 播放逻辑跑在后台 Service(Server);
    • UI 进程只是个前端壳(Client);
    • 播放 / 暂停 / 进度更新,全靠 IPC 调接口。
  • 统一数据服务
    • 做一个“数据聚合服务进程”,统一对外暴露“查数据”的接口;
    • 其他业务进程全是 Client,通过 IPC 调这个服务拿数据。
  • 安全/权限相关服务
    • 把高敏感操作放在特权进程里;
    • 普通应用想用就走 IPC + 权限校验。

4.2 RPC:多端协同、多设备联动

RPC 的典型画面大概是这样:CSDN+1

  • 手机控制家里的智能音箱、电视
    • 手机作为 Client,通过 RPC 调用音箱/电视上的服务进程;
    • 比如“播放指定歌单”“调整音量”“切换输入源”。
  • 文档多端协同编辑
    • 手机编辑器 = Client;
    • PC/平板上的文档服务 = Server;
    • 手机保存操作通过 RPC 发到 PC,那边完成真正的写文件。
  • 分布式游戏/白板
    • 每个设备上都是一个进程;
    • 通过 RPC 同步游戏状态、画笔轨迹等。

5. 开发者视角:ArkTS 里大概怎么用 IPC Kit?

现在 HarmonyOS 提供了一个 @kit.IPCKit 模块,ArkTS 这边主要是走 rpc 模块来做进程间调用。整体步骤可以概括成几步(只说思路,不按官方文档逐句翻译):developer.huawei.com+1

5.1 Client 侧(ArkTS)

  1. 引入依赖
import { rpc } from '@kit.IPCKit';
import { Want, common } from '@kit.AbilityKit';
  1. 构造 Want,准备绑定目标 Ability
const want: Want = {
  bundleName: 'com.example.demo',
  abilityName: 'DemoServiceExtAbility',
  // 如果是跨设备,还会带上 networkId
};
  1. 写 ConnectOptions,拿到 Proxy
let remote: rpc.IRemoteObject | undefined = undefined;

const connect: common.ConnectOptions = {
  onConnect(elementName, proxy) {
    remote = proxy;
    console.log('connect success');
  },
  onDisconnect(elementName) {
    remote = undefined;
  },
  onFailed(code) {
    console.error('connect failed, code = ' + code);
  }
};
  1. 在 UIAbility 里发起连接
this.context.connectAbility(want, connect);
  1. 通过 sendRequest 发送请求
async function callRemoteAdd(a: number, b: number): Promise<number> {
  if (!remote) {
    throw new Error('remote not ready');
  }
  const data = rpc.MessageParcel.create();
  const reply = rpc.MessageParcel.create();
  const option = new rpc.MessageOption();

  data.writeInt(a);
  data.writeInt(b);

  const RESULT_CODE_ADD = 1;
  await remote.sendRequest(RESULT_CODE_ADD, data, reply, option);

  return reply.readInt();
}

这里的 RESULT_CODE_ADD 就是你约定好的“方法编号”,服务端会根据这个编号走到对应的业务逻辑。

5.2 Server 侧(简要)

Server 侧一般会:developer.huawei.com+1

  1. 定义一个接口(可以用 IDL,也可以用自定义协议);
  2. 写一个继承框架基类的 Stub 类,在里面重写 onRemoteRequest
  3. 根据收到的 code + data,解析参数、调用真实业务、写回 reply;
  4. 把这个 Stub 注册成 System Ability,让 Client 能查到。

具体代码每个版本会有点差异,这里就不展开了,等后面写“实战篇”的时候再完整走一遍。


6. 那些容易踩坑的“约束与限制”

这一段官方文档写得比较“硬”,我自己理解了一下,用自己的话讲一下:developer.huawei.com+1

6.1 单条消息大小限制:别在 IPC 里硬怼大文件

  • 文档里给了一个“单设备跨进程通信时,单次传输的数据量上限”:
    • 有的地方写大约 200KB
    • 开发学习手册里给的是大约 1MBCSDN+1

不管是 200KB 还是 1MB,传递的意思都一样:

IPC/RPC 通道适合传“小消息”,不适合直接传大文件。

如果你要传的是大块数据(比如图片二进制、视频段、长列表),建议用:

  • 匿名共享内存 做大块数据;
  • IPC/RPC 只传“句柄 + 控制信息”。

这样既不会卡死 Binder,也更符合系统设计。

6.2 匿名 Stub:在 RPC 场景下别指望监听死亡

“不支持在 RPC 中订阅匿名 Stub 对象(没有向 SAMgr 注册的 Stub)的死亡通知。”

翻译成人话:

  • 如果你的 Stub 没有对 SAMgr 正式注册(就是“匿名 Stub”),在跨设备 RPC 场景下:
    • 你没法对这个 Stub 订阅“死亡通知”;
    • 换句话说,一旦远端挂了,你这边想靠死亡回调感知是不行的,只能用其它心跳/超时机制兜底。

6.3 跨设备 Proxy 不能“二次转手”

“指向远端设备 Stub 的 Proxy 对象,不能在本设备内再进行二次跨进程传递。”

这个限制很容易踩坑,比如你可能想这么干:

  1. 进程 A 通过 RPC 拿到远端设备 X 上某个服务的 Proxy;
  2. 然后 A 想把这个 Proxy,通过 IPC 传给本机另外一个进程 B,让 B 也能用这个远端服务。

**官方明确说不支持。**所以正确的姿势是:

  • 每个需要访问远端服务的本机进程,都自己去连一遍 RPC
  • 不要想着“把 Proxy 当成句柄在设备内到处传”。

6.4 Stage 模型下三方应用的限制(顺带提一句)

开发学习手册里还提到一个现实:当前版本下:CSDN

  • 三方应用暂时不能自己实现 ServiceExtensionAbility 做 Server
  • 但三方应用可以作为 Client,通过 Context 去连接系统提供的 ServiceExtensionAbility。

所以目前更常见的姿势是:

  • 你写的是一个“客户端应用”,通过 IPC/RPC 调用系统服务 / 系统能力;
  • 真正跑在后面的 Server 多半是系统应用或特权进程。

7. 我对 IPC Kit 的一个简单总结

从我目前的学习体验来说,IPC Kit 有几个很明显的特点:

  1. 理念一点都不陌生:
    本质上还是我们熟悉的“Client–Server + Proxy–Stub + Binder/SoftBus”模型,只是 HarmonyOS 把这一套封装得比较完整。
  2. 统一了“设备内”和“跨设备”的心智模型:
    • 设备内:IPC + Binder;
    • 跨设备:RPC + 软总线;
      上层开发者看的都是“拿 Proxy、发请求、收结果”,习惯之后切换场景很顺滑。
  3. 需要敬畏几个硬约束:
    • 单次传输大小限制,别当成文件传输通道;
    • 匿名 Stub 在 RPC 下做不了死亡订阅;
    • 跨设备 Proxy 不要互相转手;
    • Stage 模型下三方应用目前主要扮演 Client 角色。
  4. 和并发那一套是互补关系:
    • IPC/RPC 解决的是“进程之间怎么说话”;
    • TaskPool / Worker / Sendable / ConditionVariable 解决的是“进程内部多线程怎么协作”。
      后面做复杂业务时,两块一定是要一起玩的。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐