HarmonyOS开发中RPC通信基础:RemoteProxy与RemoteObject

举报
Jack20 发表于 2026/06/19 17:36:35 2026/06/19
【摘要】 从单进程到多进程,从本地调用到远程通信,这是鸿蒙分布式能力的基石 一、背景与动机:为什么需要RPC? 1.1 从"单打独斗"到"团队协作"想象一下这个场景:你开发了一个音乐播放器App,主界面在手机上运行,但音乐播放服务需要常驻后台。如果播放服务和UI在同一个进程里,一旦用户切换到其他App,系统可能为了省内存把整个进程杀掉——音乐就停了。这时候你会想:能不能把播放服务放到独立进程里?答案是...

从单进程到多进程,从本地调用到远程通信,这是鸿蒙分布式能力的基石

一、背景与动机:为什么需要RPC?

1.1 从"单打独斗"到"团队协作"

想象一下这个场景:你开发了一个音乐播放器App,主界面在手机上运行,但音乐播放服务需要常驻后台。如果播放服务和UI在同一个进程里,一旦用户切换到其他App,系统可能为了省内存把整个进程杀掉——音乐就停了。

这时候你会想:能不能把播放服务放到独立进程里?

答案是可以,但问题来了:UI进程怎么控制播放服务进程?它们内存是隔离的,你不能直接调用对方的方法。这就像两个办公室的同事,隔着玻璃墙,看得见喊不着。

**RPC(Remote Procedure Call,远程过程调用)**就是那部电话——让你像调用本地方法一样调用另一个进程的方法。

1.2 鸿蒙的RPC架构

鸿蒙的RPC机制基于Binder驱动实现,核心概念有两个:

概念 角色 类比
RemoteObject 服务端,被调用方 接电话的人
RemoteProxy 客户端,调用方 打电话的人

图片.png

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是服务端的基类,你需要继承它来实现具体的业务逻辑。它的核心职责:

  1. 持有Binder引用:与内核Binder驱动建立连接
  2. 处理请求:当客户端调用方法时,Binder驱动会把请求转发到这里
  3. 响应结果:处理完请求后,把结果写回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是客户端的代理类,它的工作是"欺骗"业务代码——让你以为在调用本地方法,实际它在背后做了这些事:

  1. 序列化参数:把方法参数打包成MessageParcel
  2. 发送请求:通过Binder驱动发送到服务端
  3. 等待响应:阻塞等待服务端返回结果
  4. 反序列化结果:把返回数据解析成对象
// 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 核心要点回顾

  1. RemoteObject是服务端:继承它实现业务逻辑,重写onRemoteRequest处理请求
  2. RemoteProxy是客户端:继承它封装调用逻辑,通过sendRequest发送请求
  3. MessageParcel是载体:用于序列化和反序列化数据,记得释放资源
  4. 接口契约是桥梁:定义清晰的接口和请求码,确保双方一致

6.2 最佳实践清单

  • [ ] 定义共享接口文件,避免描述符不一致
  • [ ] 使用try-finally确保MessageParcel释放
  • [ ] 读写参数顺序严格一致
  • [ ] 避免在UI线程同步调用
  • [ ] 实现断线重连机制
  • [ ] 添加详细日志便于调试
  • [ ] 使用HarmonyOS 6兼容工具类

6.3 架构图总览

图片.png


下一篇预告:《IPC机制详解:Binder在鸿蒙中的实现》——深入Binder驱动原理,理解鸿蒙通信的底层机制。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。