友情提示:以下分析均基于开源OpenHarmony代码,可能和HarmonyOS有区别

OpenHarmony 6.0 Binder通信接口设计全解析


前言:为什么需要Binder

进程隔离

每个进程有独立的虚拟地址空间,无法直接访问对方的物理内存。这是操作系统隔离保护的基础。
传统IPC方式,数据至少需要两次拷贝(用户→内核→用户),效率低,延迟高。

物理内存 (Physical RAM)
┌─────────────────────────────────────────┐
│                                         │
│   [数据...]                             │
│                                         │
│   ┌───────────────────────────────┐     │
│   │  内核空间 (Kernel)            │     │
│   │  (映射到所有进程)              │     │
│   └───────────────────────────────┘     │
│                                         │
└─────────────────────────────────────────┘
        ▲                  ▲
        │ MMU翻译           │ MMU翻译
        │                  │
┌───────┴────────┐   ┌─────┴────────┐
│ 进程A虚拟空间    │   │ 进程B虚拟空间   │
│                │   │                │
│ 0-3G: 用户空间  │   │ 0-3G: 用户空间  │
│ 3-4G: 内核空间  │   │ 3-4G: 内核空间  │
│                │   │                │
│ app code       │   │ service code   │
│ heap           │   │ heap           │
│ stack          │   │ stack          │
└────────────────┘   └────────────────┘

如果采用共享内存的方式,进程生命周期耦合、同步复杂、安全性差(可随意篡改数据)。

进程A (发送方)                  进程B (接收方)
┌─────────────────┐            ┌─────────────────┐
│ 虚拟地址:       │            │ 虚拟地址:       │
│ 0x7000: data    │            │ 0x8000: buf     │
└────────┬────────┘            └────────┬────────┘
         │ 拷贝1                         │
         ▼                               ▼
┌───────────────────────────────────────────────┐
│ 内核缓冲区 (临时存储)                         │
│ 物理地址: 0xFFFF1000                          │
└──────────┬────────────────────────────────────┘
           │ 拷贝2
┌──────────┴────────┐            ┌──────────────┐
│ 用户→内核→用户     │            │ 效率低        │
└───────────────────┘            └──────────────┘
物理内存页面: 0x5000
┌─────────────────────────┐
│ 共享数据区              │
└────┬────────────────────┘
     │
     ├──────► 映射到进程A
     │        虚拟地址: 0x6000
     │        问题: 生命周期耦合
     │        问题: 需手动同步
     │
     └──────► 映射到进程B
              虚拟地址: 0x9000
              问题: 安全性差
              问题: 易死锁

Binder核心思想是,在内核层面实现对象映射(也可以简单理解为对象映射)。

物理内存 (内核驱动管理)
┌─────────────────────────────────────────┐
│ Binder节点对象池                        │
│ ┌─────────────┐      ┌─────────────┐  │
│ │ 对象A (物理页) │      │ 对象B (物理页) │  │
│ │ 引用计数=2   │      │ 引用计数=1   │  │
│ └──────┬──────┘      └──────┬──────┘  │
└────────┼────────────────────┼──────────┘
         │                    │
┌────────▼────────┐    ┌──────▼──────────┐
│ 进程A            │    │ 进程B            │
│ 虚拟地址: 0x1000 │    │ 虚拟地址: 0x2000  │
│ (只映射引用)     │    │ (只映射引用)     │
└──────────────────┘    └──────────────────┘

通信时内核直接操作对象:
→ 零拷贝数据传递
→ 引用计数自动管理
→ UID/PID权限验证

底层驱动代码在:
drivers/staging/android/binder.c

一、接口设计

1. IRemoteBroker抽象接口

  • 核心方法
    class IRemoteBroker {
    public:
        virtual int32_t OnRemoteRequest(uint32_t code, MessageParcel& data, 
                                       MessageParcel& reply, MessageOption& option) = 0;
        virtual sptr<IRemoteObject> AsObject() = 0;
    };
    
  • 设计原则:定义统一通信契约,实现接口与实现分离
  • 版本控制:通过INTERFACE_TOKEN实现版本兼容
    const char* IMyService::GetDescriptor() {
        return "ohos.my.service.IMyService@1.0";
    }
    

2. 接口生命周期管理

  • 引用计数sptr/wptr智能指针自动管理对象生命周期
  • 死亡通知DeathRecipient回调处理服务异常终止
    class MyDeathRecipient : public IRemoteObject::DeathRecipient {
    public:
        void OnRemoteDied(const wptr<IRemoteObject>& obj) override {
            // 服务异常终止处理逻辑
        }
    };
    

二、代理-桩模式实现

1. 服务端桩类(Stub)

// MyServiceStub.h
#include "iremote_stub.h"    // 引入 IRemoteStub 模板
#include "i_my_service.h"    // 业务接口头文件

// MyServiceStub 继承 IRemoteStub 模板(而非直接继承 IRemoteBroker)
class MyServiceStub : public IRemoteStub<IMyServiceInterface> {
public:
    // 实现业务接口的纯虚函数
    int Add(int a, int b) override {
        // 实际业务逻辑
        return a + b;
    }

    std::string GetInfo() override {
        return "Service is alive";
    }

protected:
    // 重写 OnRemoteRequest,处理客户端调用分发
    int OnRemoteRequest(uint32_t code, MessageParcel &data,
                       MessageParcel &reply, MessageOption &option) override {
        switch (code) {
            case CMD_ADD: {  // 命令码:Add方法
                int a = data.ReadInt32();
                int b = data.ReadInt32();
                int result = Add(a, b);
                reply.WriteInt32(result);
                return ERR_NONE;
            }
            case CMD_GET_INFO: {  // 命令码:GetInfo方法
                std::string info = GetInfo();
                reply.WriteString(info);
                return ERR_NONE;
            }
            default:
                return IPCObjectStub::OnRemoteRequest(code, data, reply, option);
        }
    }

private:
    // 定义命令码(通常由IDL工具生成)
    enum {
        CMD_ADD = 1,
        CMD_GET_INFO = 2,
    };
};

2. 客户端代理类(Proxy)

// MyServiceProxy.h
#include "iremote_proxy.h"   // 引入 IRemoteProxy 模板

// Proxy 不继承 IRemoteObject,而是持有它的指针
class MyServiceProxy : public IRemoteProxy<IMyServiceInterface> {
public:
    // 构造函数接收 IRemoteObject(从服务管理器获取)
    explicit MyServiceProxy(const sptr<IRemoteObject> &impl) 
        : IRemoteProxy<IMyServiceInterface>(impl) {
        // impl 指向服务端的 Binder 对象
    }

    // 实现业务接口(通过 IPC 调用转发到服务端)
    int Add(int a, int b) override {
        MessageParcel data;     // 输入参数
        MessageParcel reply;    // 返回结果
        MessageOption option;   // 同步/异步选项

        // 写入参数
        data.WriteInt32(a);
        data.WriteInt32(b);

        // 通过持有的 IRemoteObject 发起跨进程调用
        // Remote() 返回保存的 IRemoteObject 指针
        int error = Remote()->SendRequest(CMD_ADD, data, reply, option);
        if (error != ERR_NONE) {
            return -1;
        }

        // 读取返回结果
        return reply.ReadInt32();
    }

    std::string GetInfo() override {
        MessageParcel data, reply;
        MessageOption option;

        Remote()->SendRequest(CMD_GET_INFO, data, reply, option);
        return reply.ReadString();
    }

private:
    // 与服务端 Stub 使用相同的命令码定义
    enum {
        CMD_ADD = 1,
        CMD_GET_INFO = 2,
    };
};

3.通信能力的封装:IRemoteObject

IRemoteObject 是 OpenHarmony/HarmonyOS(鸿蒙操作系统)IPC 机制中的核心抽象接口,与 Android 的 IBinder 概念类似。这个其实才是对Binder驱动的封装。

class IRemoteObject : public virtual Parcelable, public virtual RefBase {
    // 提供跨进程通信的基础能力
}
  • 基础抽象:所有可跨进程传递的对象都必须继承自 IRemoteObject
  • 不能直接实例化:它是抽象类,定义接口规范,由 IRemoteStub(服务端)和 IRemoteProxy(客户端)具体实
    注意:IRemoteObject和IRemoteBroker不是一个东西,IRemoteBroker主要是定义业务接口抽象,IRemoteObject是定义底层通信能力抽象。
    关键点:IRemoteObject 是连接 IRemoteProxy 和 IRemoteStub 的桥梁,客户端和服务端都通过它发送/接收 IPC 消息。如下图:
客户端进程                           服务端进程
┌─────────────────┐                ┌─────────────────┐
│ IRemoteProxy    │                │ IRemoteStub     │
│ (继承并调用IRemoteObject)│                │ (继承IRemoteObject)│
└───────┬─────────┘                └────────┬────────┘
        │ SendRequest()                      │
        │───────────────────────────────────>│
        │  通过Binder驱动传递                │
        │                                    │
        │<───────────────────────────────────│
        │  返回结果                          │
        │                                    │
        ▼                                    ▼
   IRemoteObject 是通信的"信道"而非"数据"

关键类之间的关系:

类名 与 IRemoteObject 的关系 职责
IRemoteBroker 无关(接口契约) 定义服务接口方法
IRemoteStub 继承 IRemoteObject 服务端桩,接收并处理请求
IRemoteProxy 持有 IRemoteObject 客户端代理,封装 SendRequest 调用
IPCObjectProxy 实现类 Binder 框架实例化的代理对象
IPCObjectStub 实现类 Binder 框架实例化的桩对象
与android的区别:
特性 OpenHarmony IRemoteObject Android IBinder
-------- ------------------------------ -------------------
系统 鸿蒙 OS Android
驱动 Binder 驱动(类似) Binder 驱动
服务管理 System Ability Manager (SAMgr) ServiceManager
语言 C++ 为主 Java/C++ 混合

4.类设计

                              ┌─────────────────┐
                              │  DeathRecipient │
                              │  (死亡通知接口)  │
                              └────────┬────────┘
                                       │ 注册/回调
                                       ▼
┌──────────────────────────────────────────────────────────────────────────────┐
│  鸿蒙 IPC 框架核心类关系                                                     │
│                                                                              │
│  IRemoteBroker      RefBase          Parcelable      MessageParcel          │
│       ▲                ▲                  ▲                ▲                │
│       │                │                  │                │                │
│       └───────┬────────┴────────┬─────────┴────────────────┼────────────────┘
│               │                 │                          │
│               ▼                 ▼                          ▼
│        IMyServiceInterface  IRemoteObject  ────────  MessageOption          │
│               ▲                 ▲  ▲                     ▲                  │
│               │                 │  │                     │                  │
│     ┌─────────┴─────────┐       │  │                     │                  │
│     │                   │       │  │                     │                  │
│     ▼                   ▼       │  │                     │                  │
│  IRemoteStub<T>   IRemoteProxy<T> │  │                     │                  │
│     ▲                   ▲        │  │                     │                  │
│     │                   │        │  │ 持有               │ 使用             │
│     ▼                   ▼        │  │                     │                  │
│  MyServiceStub   MyServiceProxy─┘  │                     │                  │
│                                      │                     │                  │
└──────────────────────────────────────┼─────────────────────┼──────────────────┘
                                       │                     │
                                       │ 通过 Binder 驱动通信 │
                                       ▼                     ▼
                                ┌──────────────────────────────────┐
                                │  Binder Driver (Kernel)         │
                                │  • 进程间对象引用管理            │
                                │  • 数据传递(零拷贝)            │
                                │  • 权限验证 (UID/PID)            │
                                └──────────────────────────────────┘

三、数据契约设计

1. Parcelable接口规范

class Parcelable {
public:
    virtual bool Marshalling(MessageParcel& parcel) const = 0;
    virtual bool Unmarshalling(MessageParcel& parcel) = 0;
};

2. 复杂对象序列化示例

class MyData : public Parcelable {
public:
    bool Marshalling(MessageParcel& parcel) const override {
        parcel.WriteString(name_);
        parcel.WriteInt32(age_);
        return true;
    }
    // ...Unmarshalling实现...
private:
    std::string name_;
    int32_t age_;
};

3.共享内存类对象打包

DMA共享内存对象通常包含:

  • 文件描述符(fd):指向dma_buf的句柄
  • 物理地址/虚拟地址:内存区域地址
  • 大小(size):内存区域长度
  • 缓存同步信息:CPU/GPU缓存一致性状态
    不能直接打包的原因:
  • fd是进程内有效的整数句柄,跨进程后无效
  • 虚拟地址在不同进程地址空间中映射不同
  • 需要让内核重新映射共享内存到目标进程
    数据发送的时候,需要通过发送句柄的方式,让接收方线程重新mmap:
class DmaBuffer : public Parcelable {
public:
    int fd;           // dma_buf 文件描述符
    size_t size;
    void* virt_addr;

    // 序列化:打包fd而非内存数据
    bool Marshalling(MessageParcel& parcel) const override {
        // 关键:将fd传递给内核,让内核在目标进程重建fd
        bool ok = parcel.WriteFileDescriptor(fd);
        ok &= parcel.WriteUint64(size);
        // 不打包virt_addr,因为目标进程会重新mmap
        return ok;
    }

    // 反序列化:接收fd并重新映射
    bool Unmarshalling(MessageParcel& parcel) override {
        // 从parcel读取fd(内核已在目标进程创建新fd)
        fd = parcel.ReadFileDescriptor();
        size = parcel.ReadUint64();
        
        // 重要:在目标进程重新mmap内存映射
        virt_addr = mmap(nullptr, size, PROT_READ | PROT_WRITE, 
                        MAP_SHARED, fd, 0);
        return fd >= 0 && virt_addr != MAP_FAILED;
    }

    ~DmaBuffer() {
        if (virt_addr) munmap(virt_addr, size);
        if (fd >= 0) close(fd);
    }
};

4.指针类对象打包

同dma buffer,指针也不能直接打包发送到对端。也是需要继承Parcelable,在Marshalling、Unmarshalling中完成对象的打包和解析。

// 步骤1:让对象继承 RefBase 和 Parcelable
class MyData : public RefBase, public Parcelable {
public:
    int value;
    std::string name;

    // 实现序列化
    bool Marshalling(MessageParcel& parcel) const override {
        parcel.WriteInt32(value);
        parcel.WriteString(name);
        return true;
    }

    // 实现反序列化
    bool Unmarshalling(MessageParcel& parcel) override {
        value = parcel.ReadInt32();
        name = parcel.ReadString();
        return true;
    }
};

// 步骤2:使用 sptr 替代 shared_ptr
void SendData(sptr<MyData> data) {  // sptr 管理 RefBase 对象
    MessageParcel parcel;
    parcel.WriteParcelable(data.GetRefPtr());  // ✅ 正确
}

// 步骤3:在接收方重建
sptr<MyData> ReceiveData(MessageParcel& parcel) {
    sptr<MyData> data = new MyData();  // 创建对象
    parcel.ReadParcelable(data.GetRefPtr());  // 填充数据
    return data;
}

四、接口调用的一般流程

服务注册流程

  1. 实现IRemoteBroker接口
  2. 创建Binder对象并设置死亡通知
  3. 调用SAManagerClient::AddService()注册
  4. Binder驱动创建binder_node结构
  5. 服务信息写入ServiceManager并广播

客户端调用流程

  1. 调用SAManagerClient::GetService()获取代理
  2. 生成binder_ref并建立引用计数
  3. 构造MessageParcel并验证接口Token
  4. 通过Binder驱动发送BC_TRANSACTION
  5. 服务端接收BR_TRANSACTION并触发OnRemoteRequest

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐