HarmonyOS开发中RPC通信基础:RemoteProxy与RemoteObject
从单进程到多进程,从本地调用到远程通信,这是鸿蒙分布式能力的基石
一、背景与动机:为什么需要RPC?
1.1 从"单打独斗"到"团队协作"
想象一下这个场景:你开发了一个音乐播放器App,主界面在手机上运行,但音乐播放服务需要常驻后台。如果播放服务和UI在同一个进程里,一旦用户切换到其他App,系统可能为了省内存把整个进程杀掉——音乐就停了。
这时候你会想:能不能把播放服务放到独立进程里?
答案是可以,但问题来了:UI进程怎么控制播放服务进程?它们内存是隔离的,你不能直接调用对方的方法。这就像两个办公室的同事,隔着玻璃墙,看得见喊不着。
**RPC(Remote Procedure Call,远程过程调用)**就是那部电话——让你像调用本地方法一样调用另一个进程的方法。
1.2 鸿蒙的RPC架构
鸿蒙的RPC机制基于Binder驱动实现,核心概念有两个:
| 概念 | 角色 | 类比 |
|---|---|---|
| RemoteObject | 服务端,被调用方 | 接电话的人 |
| RemoteProxy | 客户端,调用方 | 打电话的人 |

1.3 RPC vs IPC vs 本地调用
很多开发者分不清RPC和IPC的区别,这里澄清一下:
- IPC(Inter-Process Communication):进程间通信的统称,包括共享内存、管道、Socket、Binder等
- RPC:IPC的一种实现方式,让远程调用像本地调用一样自然
- 本地调用:同一进程内的方法调用,直接访问内存
// 本地调用:直接访问
player.play() // 同一进程,直接调用
// RPC调用:看起来一样,实际跨进程
playerProxy.play() // 跨进程,通过Binder转发
二、核心原理:RemoteProxy与RemoteObject如何配合
2.1 RemoteObject:服务端的"接线员"
RemoteObject是服务端的基类,你需要继承它来实现具体的业务逻辑。它的核心职责:
- 持有Binder引用:与内核Binder驱动建立连接
- 处理请求:当客户端调用方法时,Binder驱动会把请求转发到这里
- 响应结果:处理完请求后,把结果写回Binder驱动
// RemoteObject的核心结构(伪代码)
abstract class RemoteObject {
// Binder引用,由系统创建
private readonly binder: nativeBinder
// 处理远程请求的入口
onRemoteRequest(code: number, data: MessageParcel, reply: MessageParcel): boolean {
// 子类重写这个方法处理具体请求
return false
}
// 获取描述符,用于身份识别
getInterfaceDescriptor(): string {
return this.descriptor
}
}
2.2 RemoteProxy:客户端的"代理人"
RemoteProxy是客户端的代理类,它的工作是"欺骗"业务代码——让你以为在调用本地方法,实际它在背后做了这些事:
- 序列化参数:把方法参数打包成MessageParcel
- 发送请求:通过Binder驱动发送到服务端
- 等待响应:阻塞等待服务端返回结果
- 反序列化结果:把返回数据解析成对象
// RemoteProxy的核心结构(伪代码)
abstract class RemoteProxy {
// 持有服务端的Binder引用
private readonly remote: IRemoteObject
// 发送远程请求
sendRequest(code: number, data: MessageParcel, reply: MessageParcel): boolean {
// 1. 序列化参数
// 2. 调用Binder驱动
// 3. 等待响应
// 4. 反序列化结果
return this.remote.sendRequest(code, data, reply)
}
}
2.3 通信流程详解
sequenceDiagram
participant Client as 客户端业务代码
participant Proxy as RemoteProxy
participant Binder as Binder驱动
participant Object as RemoteObject
participant Server as 服务端业务代码
Client->>Proxy: 调用play()方法
Proxy->>Proxy: 序列化参数到MessageParcel
Proxy->>Binder: sendRequest(code=1, data)
Binder->>Binder: 跨进程内存拷贝
Binder->>Object: onRemoteRequest(code=1, data)
Object->>Server: 执行play()逻辑
Server-->>Object: 返回结果
Object->>Object: 结果写入MessageParcel
Object-->>Binder: 返回reply
Binder->>Binder: 跨进程内存拷贝
Binder-->>Proxy: 返回reply
Proxy->>Proxy: 反序列化结果
Proxy-->>Client: 返回结果
classDef primary fill:#e3f2fd,stroke:#1565c0,stroke-width:2px
classDef warning fill:#fff8e1,stroke:#f57f17,stroke-width:2px
classDef success fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
class Client,Server primary
class Proxy,Object warning
class Binder success
2.4 关键数据结构:MessageParcel
MessageParcel是RPC通信的"快递包裹",用于承载序列化后的数据:
// MessageParcel的主要方法
class MessageParcel {
// 写入数据
writeInt(value: number): void
writeString(value: string): void
writeBoolean(value: boolean): void
writeByteArray(value: number[]): void
writeRemoteObject(object: IRemoteObject): void
// 读取数据
readInt(): number
readString(): string
readBoolean(): boolean
readByteArray(): number[]
readRemoteObject(): IRemoteObject
// 获取/设置文件描述符(用于传递文件)
readFileDescriptor(): number
writeFileDescriptor(fd: number): void
}
三、代码实战:实现一个跨进程计数器
3.1 定义接口契约
首先定义一个接口,让客户端和服务端达成"协议":
// ICounter.ets
import { IRemoteObject } from '@kit.IPCKit'
/**
* 计数器接口定义
* 客户端和服务端都需要遵循这个契约
*/
export interface ICounter {
/**
* 获取当前计数值
* @returns 当前计数
*/
getCount(): number
/**
* 增加计数
* @param delta 增量值
* @returns 增加后的计数
*/
increment(delta: number): number
/**
* 重置计数器
*/
reset(): void
/**
* 获取RemoteObject引用(用于跨进程传递)
*/
asObject(): IRemoteObject
}
// 定义请求码,用于区分不同的方法调用
export const enum CounterCode {
GET_COUNT = 1,
INCREMENT = 2,
RESET = 3
}
// 定义接口描述符,用于身份验证
export const COUNTER_DESCRIPTOR = 'com.example.ICounter'
3.2 实现服务端:CounterRemoteObject
// CounterRemoteObject.ets
import { RemoteObject, MessageParcel, IRemoteObject } from '@kit.IPCKit'
import { ICounter, CounterCode, COUNTER_DESCRIPTOR } from './ICounter'
import hilog from '@ohos.hilog'
const TAG = 'CounterRemoteObject'
const DOMAIN = 0xFF00
/**
* 计数器服务端实现
* 继承RemoteObject,处理客户端的远程调用请求
*/
export class CounterRemoteObject extends RemoteObject implements ICounter {
// 计数器状态,保存在服务端进程中
private count: number = 0
constructor() {
// 调用父类构造函数,传入接口描述符
super(COUNTER_DESCRIPTOR)
hilog.info(DOMAIN, TAG, 'CounterRemoteObject created')
}
/**
* 处理远程请求的核心方法
* 当客户端调用方法时,请求会转发到这里
*
* @param code 请求码,标识调用哪个方法
* @param data 请求数据,包含方法参数
* @param reply 响应数据,用于返回结果
* @returns 是否处理成功
*/
onRemoteRequest(code: number, data: MessageParcel, reply: MessageParcel): boolean {
hilog.info(DOMAIN, TAG, `onRemoteRequest called, code: ${code}`)
switch (code) {
case CounterCode.GET_COUNT:
return this.handleGetCount(reply)
case CounterCode.INCREMENT:
return this.handleIncrement(data, reply)
case CounterCode.RESET:
return this.handleReset(reply)
default:
hilog.error(DOMAIN, TAG, `Unknown code: ${code}`)
return false
}
}
/**
* 处理getCount请求
*/
private handleGetCount(reply: MessageParcel): boolean {
// 将当前计数写入响应
reply.writeInt(this.count)
hilog.info(DOMAIN, TAG, `getCount: ${this.count}`)
return true
}
/**
* 处理increment请求
*/
private handleIncrement(data: MessageParcel, reply: MessageParcel): boolean {
// 从请求数据中读取增量值
const delta = data.readInt()
this.count += delta
// 将新计数写入响应
reply.writeInt(this.count)
hilog.info(DOMAIN, TAG, `increment: +${delta}, new count: ${this.count}`)
return true
}
/**
* 处理reset请求
*/
private handleReset(reply: MessageParcel): boolean {
this.count = 0
reply.writeInt(0) // 返回重置后的值
hilog.info(DOMAIN, TAG, 'reset: count = 0')
return true
}
/**
* 实现ICounter接口的方法
* 这些方法供本地调用使用(服务端进程内)
*/
getCount(): number {
return this.count
}
increment(delta: number): number {
this.count += delta
return this.count
}
reset(): void {
this.count = 0
}
/**
* 返回自身的RemoteObject引用
* 用于传递给客户端
*/
asObject(): IRemoteObject {
return this
}
}
3.3 实现客户端:CounterProxy
// CounterProxy.ets
import { RemoteProxy, MessageParcel, IRemoteObject } from '@kit.IPCKit'
import { ICounter, CounterCode, COUNTER_DESCRIPTOR } from './ICounter'
import hilog from '@ohos.hilog'
const TAG = 'CounterProxy'
const DOMAIN = 0xFF00
/**
* 计数器客户端代理
* 继承RemoteProxy,将方法调用转发到服务端
*/
export class CounterProxy extends RemoteProxy implements ICounter {
constructor(remote: IRemoteObject) {
super(remote)
hilog.info(DOMAIN, TAG, 'CounterProxy created')
}
/**
* 获取当前计数
* 通过RPC调用服务端的getCount方法
*/
getCount(): number {
// 创建请求数据包(无参数)
const data = MessageParcel.create()
// 创建响应数据包
const reply = MessageParcel.create()
try {
// 发送远程请求
const result = this.sendRequest(CounterCode.GET_COUNT, data, reply)
if (!result) {
hilog.error(DOMAIN, TAG, 'getCount failed: sendRequest returned false')
return -1
}
// 读取响应数据
return reply.readInt()
} finally {
// 释放资源
data.reclaim()
reply.reclaim()
}
}
/**
* 增加计数
* 通过RPC调用服务端的increment方法
*
* @param delta 增量值
*/
increment(delta: number): number {
const data = MessageParcel.create()
const reply = MessageParcel.create()
try {
// 将参数写入请求数据包
data.writeInt(delta)
// 发送远程请求
const result = this.sendRequest(CounterCode.INCREMENT, data, reply)
if (!result) {
hilog.error(DOMAIN, TAG, 'increment failed')
return -1
}
// 读取响应数据
return reply.readInt()
} finally {
data.reclaim()
reply.reclaim()
}
}
/**
* 重置计数器
* 通过RPC调用服务端的reset方法
*/
reset(): void {
const data = MessageParcel.create()
const reply = MessageParcel.create()
try {
// 发送远程请求(无参数)
this.sendRequest(CounterCode.RESET, data, reply)
} finally {
data.reclaim()
reply.reclaim()
}
}
/**
* 返回底层的RemoteObject引用
*/
asObject(): IRemoteObject {
return this.remote
}
/**
* 静态工厂方法:从IRemoteObject创建代理
* 这是获取代理的标准方式
*/
static asInterface(remote: IRemoteObject): ICounter {
if (remote === null || remote === undefined) {
return null
}
// 检查是否已经是本地对象(同一进程)
if (remote.getInterfaceDescriptor() === COUNTER_DESCRIPTOR) {
return remote as ICounter
}
// 创建代理对象
return new CounterProxy(remote)
}
}
3.4 服务端发布与客户端连接
// CounterService.ets - 服务端ServiceAbility
import { Ability } from '@kit.AbilityKit'
import { CounterRemoteObject } from './CounterRemoteObject'
import hilog from '@ohos.hilog'
const TAG = 'CounterService'
const DOMAIN = 0xFF00
/**
* 计数器服务Ability
* 运行在独立进程中,提供计数器服务
*/
export default class CounterService extends Ability {
// 计数器服务端对象
private counter: CounterRemoteObject = null
onCreate(want, launchParam) {
hilog.info(DOMAIN, TAG, 'CounterService onCreate')
// 创建计数器服务端
this.counter = new CounterRemoteObject()
}
/**
* 当客户端连接时调用
* 返回RemoteObject给客户端
*/
onConnect(want) {
hilog.info(DOMAIN, TAG, 'CounterService onConnect')
// 返回计数器的RemoteObject,客户端通过它创建Proxy
return this.counter.asObject()
}
onDisconnect(want) {
hilog.info(DOMAIN, TAG, 'CounterService onDisconnect')
}
onDestroy() {
hilog.info(DOMAIN, TAG, 'CounterService onDestroy')
this.counter = null
}
}
// CounterClient.ets - 客户端使用示例
import { common } from '@kit.AbilityKit'
import { ICounter } from './ICounter'
import { CounterProxy } from './CounterProxy'
import hilog from '@ohos.hilog'
const TAG = 'CounterClient'
const DOMAIN = 0xFF00
/**
* 计数器客户端使用示例
*/
export class CounterClient {
private context: common.UIAbilityContext
private counter: ICounter = null
private connectionId: number = -1
constructor(context: common.UIAbilityContext) {
this.context = context
}
/**
* 连接到计数器服务
*/
connectService(): Promise<void> {
return new Promise((resolve, reject) => {
const want = {
bundleName: 'com.example.counter',
abilityName: 'CounterService'
}
const connectOptions = {
// 连接成功回调
onConnect: (elementName, remote) => {
hilog.info(DOMAIN, TAG, `Connected to ${elementName.abilityName}`)
// 从RemoteObject创建代理
this.counter = CounterProxy.asInterface(remote)
resolve()
},
// 连接断开回调
onDisconnect: (elementName) => {
hilog.info(DOMAIN, TAG, `Disconnected from ${elementName.abilityName}`)
this.counter = null
},
// 连接失败回调
onFailed: () => {
hilog.error(DOMAIN, TAG, 'Failed to connect service')
reject(new Error('Connection failed'))
}
}
// 发起连接
this.connectionId = this.context.connectServiceExtensionAbility(want, connectOptions)
})
}
/**
* 断开连接
*/
disconnectService() {
if (this.connectionId !== -1) {
this.context.disconnectServiceExtensionAbility(this.connectionId)
this.connectionId = -1
}
}
/**
* 使用计数器
*/
async useCounter() {
if (!this.counter) {
hilog.error(DOMAIN, TAG, 'Counter not connected')
return
}
// 获取当前计数
const current = this.counter.getCount()
hilog.info(DOMAIN, TAG, `Current count: ${current}`)
// 增加计数
const newCount = this.counter.increment(10)
hilog.info(DOMAIN, TAG, `After increment: ${newCount}`)
// 重置计数器
this.counter.reset()
hilog.info(DOMAIN, TAG, 'Counter reset')
}
}
四、踩坑与注意事项
4.1 坑一:忘记释放MessageParcel
问题:MessageParcel是系统资源,不释放会导致内存泄漏甚至进程崩溃。
// ❌ 错误示范:忘记释放
getCount(): number {
const data = MessageParcel.create()
const reply = MessageParcel.create()
this.sendRequest(CounterCode.GET_COUNT, data, reply)
return reply.readInt()
// data和reply没有释放!
}
// ✅ 正确做法:使用try-finally确保释放
getCount(): number {
const data = MessageParcel.create()
const reply = MessageParcel.create()
try {
this.sendRequest(CounterCode.GET_COUNT, data, reply)
return reply.readInt()
} finally {
data.reclaim() // 无论是否异常,都会释放
reply.reclaim()
}
}
4.2 坑二:参数读写顺序不一致
问题:服务端读取参数的顺序必须与客户端写入的顺序完全一致,否则数据错乱。
// 客户端写入顺序:int, string, boolean
data.writeInt(100)
data.writeString('hello')
data.writeBoolean(true)
// ❌ 服务端读取顺序错误:string, int, boolean
const str = data.readString() // 读到的是int的二进制表示!
const num = data.readInt() // 读到的是string的地址!
const flag = data.readBoolean()
// ✅ 服务端读取顺序必须一致:int, string, boolean
const num = data.readInt() // 100
const str = data.readString() // 'hello'
const flag = data.readBoolean() // true
4.3 坑三:接口描述符不匹配
问题:客户端和服务端的接口描述符必须一致,否则身份验证失败。
// ❌ 服务端和客户端描述符不一致
// 服务端
export const COUNTER_DESCRIPTOR = 'com.example.ICounter'
// 客户端(拼写错误)
export const COUNTER_DESCRIPTOR = 'com.example.ICouter' // 少了个n
// ✅ 最佳实践:定义在共享文件中,双方引用同一个常量
// ICounter.ets(共享文件)
export const COUNTER_DESCRIPTOR = 'com.example.ICounter'
4.4 坑四:同步调用阻塞UI线程
问题:RPC调用是同步阻塞的,在UI线程调用会导致界面卡顿。
// ❌ 在UI线程直接调用
@Entry
@Component
struct CounterPage {
build() {
Button('增加计数')
.onClick(() => {
// 阻塞UI线程!
this.counter.increment(1)
})
}
}
// ✅ 在工作线程调用
import taskpool from '@ohos.taskpool'
@Entry
@Component
struct CounterPage {
async incrementCounter() {
// 在taskpool中执行RPC调用
await taskpool.execute(() => {
this.counter.increment(1)
})
}
build() {
Button('增加计数')
.onClick(() => {
this.incrementCounter() // 不阻塞UI
})
}
}
4.5 坑五:进程被杀后未重连
问题:服务端进程可能被系统杀死,客户端需要处理断线重连。
export class CounterClient {
private counter: ICounter = null
private isConnecting: boolean = false
/**
* 安全调用计数器方法
* 自动处理重连逻辑
*/
async safeCall<T>(action: (counter: ICounter) => T): Promise<T> {
// 如果未连接,先连接
if (!this.counter && !this.isConnecting) {
await this.connectService()
}
try {
return action(this.counter)
} catch (error) {
// 调用失败,可能是服务端已断开
hilog.error(DOMAIN, TAG, `Call failed: ${error.message}`)
// 重置连接状态,下次调用时重连
this.counter = null
throw error
}
}
// 使用示例
async getCount(): Promise<number> {
return this.safeCall(counter => counter.getCount())
}
}
五、HarmonyOS 6适配指南
5.1 API变更
HarmonyOS 6对IPC Kit进行了重要更新:
| 变更项 | HarmonyOS 5 | HarmonyOS 6 |
|---|---|---|
| 导入方式 | import { RemoteObject } from '@ohos.rpc' |
import { RemoteObject } from '@kit.IPCKit' |
| MessageParcel创建 | new MessageParcel() |
MessageParcel.create() |
| 异步支持 | 仅同步调用 | 新增sendRequestAsync()异步方法 |
| 错误处理 | 返回boolean | 抛出异常,需try-catch |
5.2 行为变更
1. MessageParcel资源管理更严格
// HarmonyOS 5:不释放也能运行(但不推荐)
const data = new MessageParcel()
// HarmonyOS 6:必须释放,否则警告日志
const data = MessageParcel.create()
// ... 使用后必须调用
data.reclaim()
2. 连接超时时间调整
// HarmonyOS 6新增连接选项
const connectOptions = {
onConnect: (elementName, remote) => { /* ... */ },
onDisconnect: (elementName) => { /* ... */ },
onFailed: () => { /* ... */ },
// 新增:连接超时时间(毫秒)
connectTimeout: 5000, // 默认10秒,可自定义
// 新增:是否自动重连
autoReconnect: true
}
5.3 适配代码示例
// 兼容HarmonyOS 5和6的工具类
import { RemoteObject, MessageParcel, IRemoteObject } from '@kit.IPCKit'
export class RPCCompat {
/**
* 创建MessageParcel(兼容两个版本)
*/
static createMessageParcel(): MessageParcel {
// HarmonyOS 6使用静态方法
if (typeof MessageParcel.create === 'function') {
return MessageParcel.create()
}
// HarmonyOS 5使用构造函数
return new MessageParcel()
}
/**
* 安全释放MessageParcel
*/
static reclaim(parcel: MessageParcel): void {
if (parcel && typeof parcel.reclaim === 'function') {
parcel.reclaim()
}
}
/**
* 异步发送请求(HarmonyOS 6)
* 回退到同步调用(HarmonyOS 5)
*/
static async sendRequest(
proxy: RemoteProxy,
code: number,
data: MessageParcel,
reply: MessageParcel
): Promise<boolean> {
// HarmonyOS 6支持异步
if (typeof proxy.sendRequestAsync === 'function') {
return proxy.sendRequestAsync(code, data, reply)
}
// HarmonyOS 5使用同步
return proxy.sendRequest(code, data, reply)
}
}
六、总结
6.1 核心要点回顾
- RemoteObject是服务端:继承它实现业务逻辑,重写
onRemoteRequest处理请求 - RemoteProxy是客户端:继承它封装调用逻辑,通过
sendRequest发送请求 - MessageParcel是载体:用于序列化和反序列化数据,记得释放资源
- 接口契约是桥梁:定义清晰的接口和请求码,确保双方一致
6.2 最佳实践清单
- [ ] 定义共享接口文件,避免描述符不一致
- [ ] 使用try-finally确保MessageParcel释放
- [ ] 读写参数顺序严格一致
- [ ] 避免在UI线程同步调用
- [ ] 实现断线重连机制
- [ ] 添加详细日志便于调试
- [ ] 使用HarmonyOS 6兼容工具类
6.3 架构图总览

下一篇预告:《IPC机制详解:Binder在鸿蒙中的实现》——深入Binder驱动原理,理解鸿蒙通信的底层机制。
- 点赞
- 收藏
- 关注作者
评论(0)