HarmonyOS 鸿蒙Next跨进程通信—IPC与RPC通信开发

发布于 1周前 作者 yibo5220 来自 鸿蒙OS

HarmonyOS 鸿蒙Next跨进程通信—IPC与RPC通信开发

IPC与RPC通信概述

基本概念

IPC(Inter-Process Communication)与RPC(Remote Procedure Call)用于实现跨进程通信,不同的是前者使用Binder驱动,用于设备内的跨进程通信,后者使用软总线驱动,用于跨设备跨进程通信。需要跨进程通信的原因是因为每个进程都有自己独立的资源和内存空间,其他进程不能随意访问不同进程的内存和资源,IPC/RPC便是为了突破这一点。IPC和RPC通常采用客户端-服务器(Client-Server)模型,在使用时,请求服务的(Client)一端进程可获取提供服务(Server)一端所在进程的代理(Proxy),并通过此代理读写数据来实现进程间的数据通信,更具体的讲,首先请求服务的(Client)一端会建立一个服务提供端(Server)的代理对象,这个代理对象具备和服务提供端(Server)一样的功能,若想访问服务提供端(Server)中的某一个方法,只需访问代理对象中对应的方法即可,代理对象会将请求发送给服务提供端(Server);然后服务提供端(Server)处理接受到的请求,处理完之后通过驱动返回处理结果给代理对象;最后代理对象将请求结果进一步返回给请求服务端(Client)。通常,Server会先注册系统能力(System Ability)到系统能力管理者(System Ability Manager,缩写SAMgr)中,SAMgr负责管理这些SA并向Client提供相关的接口。Client要和某个具体的SA通信,必须先从SAMgr中获取该SA的代理,然后使用代理和SA通信。下文直接使用Proxy表示服务请求方,Stub表示服务提供方。

约束与限制

  • 单个设备上跨进程通信时,传输的数据量最大约为1MB,过大的数据量请使用匿名共享内存
  • 不支持在RPC中订阅匿名Stub对象(没有向SAMgr注册Stub对象)的死亡通知。
  • 不支持把跨设备的Proxy对象传递回该Proxy对象所指向的Stub对象所在的设备,即指向远端设备Stub的Proxy对象不能在本设备内进行二次跨进程传递。

使用建议

首先,需要编写接口类,接口类中必须定义消息码,供通信双方标识操作,可以有未实现的方法,因为通信双方均需继承该接口类且双方不能是抽象类,所以此时定义的未实现的方法必须在双方继承时给出实现,这保证了继承双方不是抽象类。然后,需要编写Stub端相关类及其接口,并且实现AsObject方法及OnRemoteRequest方法。同时,也需要编写Proxy端,实现接口类中的方法和AsObject方法,也可以封装一些额外的方法用于调用SendRequest向对端发送数据。以上三者都具备后,便可以向SAMgr注册SA了,此时的注册应该在Stub所在进程完成。最后,在需要的地方从SAMgr中获取Proxy,便可通过Proxy实现与Stub的跨进程通信了。

相关步骤:

  • 实现接口类:需继承IRemoteBroker,需定义消息码,可声明不在此类实现的方法。
  • 实现服务提供端(Stub):需继承IRemoteStub或者RemoteObject,需重写AsObject方法及OnRemoteRequest方法。
  • 实现服务请求端(Proxy):需继承IRemoteProxy或RemoteProxy,需重写AsObject方法,封装所需方法调用SendRequest。
  • 注册SA:申请SA的唯一ID,向SAMgr注册SA。
  • 获取SA:通过SA的ID和设备ID获取Proxy,使用Proxy与远端通信

IPC与RPC通信开发指导

场景介绍

IPC/RPC的主要工作是让运行在不同进程的Proxy和Stub互相通信,包括Proxy和Stub运行在不同设备的情况。

接口说明

表1 Native侧IPC接口

类/接口 方法 功能说明
IRemoteBroker sptr AsObject() 返回通信对象。Stub端返回RemoteObject对象本身,Proxy端返回代理对象。
IRemoteStub virtual int OnRemoteRequest(uint32_t code, MessageParcel &data, MessageParcel &reply, MessageOption &option) 请求处理方法,派生类需要重写该方法用来处理Proxy的请求并返回结果。
IRemoteProxy Remote()->SendRequest(code, data, reply, option) 消息发送方法,业务的Proxy类需要从IRemoteProxy类派生,该方法用来向对端发送消息。

开发步骤

Native侧开发步骤

  1. 添加依赖

    SDK依赖:

    #ipc场景
    external_deps = [
      "ipc:ipc_single",
    ]
    
    #rpc场景
    external_deps = [
      "ipc:ipc_core",
    ]
    

    此外, IPC/RPC依赖的refbase实现在公共基础库下,请增加对utils的依赖:

    external_deps = [
      "c_utils:utils",
    ]
    
  2. 定义IPC接口ITestAbility

    SA接口继承IPC基类接口IRemoteBroker,接口里定义描述符、业务函数和消息码,其中业务函数在Proxy端和Stub端都需要实现。

    #include "iremote_broker.h"
    
    //定义消息码
    const int TRANS_ID_PING_ABILITY = 5;
    
    const std::string DESCRIPTOR = "test.ITestAbility";
    
    class ITestAbility : public IRemoteBroker {
    public:
        // DECLARE_INTERFACE_DESCRIPTOR是必需的,入参需使用std::u16string;
        DECLARE_INTERFACE_DESCRIPTOR(to_utf16(DESCRIPTOR));
        virtual int TestPingAbility(const std::u16string &dummy) = 0; // 定义业务函数
    };
    
  3. 定义和实现服务端TestAbilityStub

    该类是和IPC框架相关的实现,需要继承 IRemoteStub。Stub端作为接收请求的一端,需重写OnRemoteRequest方法用于接收客户端调用。

    #include "iability_test.h"
    #include "iremote_stub.h"
    
    class TestAbilityStub : public IRemoteStub<ITestAbility> {
    public:
        virtual int OnRemoteRequest(uint32_t code, MessageParcel &data, MessageParcel &reply, MessageOption &option) override;
        int TestPingAbility(const std::u16string &dummy) override;
    };
    
    int TestAbilityStub::OnRemoteRequest(uint32_t code,
        MessageParcel &data, MessageParcel &reply, MessageOption &option)
    {
        switch (code) {
            case TRANS_ID_PING_ABILITY: {
                std::u16string dummy = data.ReadString16();
                int result = TestPingAbility(dummy);
                reply.WriteInt32(result);
                return 0;
            }
            default:
                return IPCObjectStub::OnRemoteRequest(code, data, reply, option);
        }
    }
    
  4. 定义服务端业务函数具体实现类TestAbility

    #include "iability_server_test.h"
    
    class TestAbility : public TestAbilityStub {
    public:
        int TestPingAbility(const std::u16string &dummy);
    }
    
    int TestAbility::TestPingAbility(const std::u16string &dummy) {
        return 0;
    }
    
  5. 定义和实现客户端 TestAbilityProxy

    该类是Proxy端实现,继承IRemoteProxy,调用SendRequest接口向Stub端发送请求,对外暴露服务端提供的能力。

    #include "iability_test.h"
    #include "iremote_proxy.h"
    #include "iremote_object.h"
    
    class TestAbilityProxy : public IRemoteProxy<ITestAbility> {
    public:
        explicit TestAbilityProxy(const sptr<IRemoteObject> &impl);
        int TestPingAbility(const std::u16string &dummy) override;
    private:
        static inline BrokerDelegator<TestAbilityProxy> delegator_; // 方便后续使用iface_cast宏
    }
    
    TestAbilityProxy::TestAbilityProxy(const sptr<IRemoteObject> &impl)
        : IRemoteProxy<ITestAbility>(impl)
    {
    }
    
    int TestAbilityProxy::TestPingAbility(const std::u16string &dummy){
        MessageOption option;
        MessageParcel dataParcel, replyParcel;
        dataParcel.WriteString16(dummy);
        int error = Remote()->SendRequest(TRANS_ID_PING_ABILITY, dataParcel, replyParcel, option);
        int result = (error == ERR_NONE) ? replyParcel.ReadInt32() : -1;
        return result;
    }
    
  6. SA注册与启动

    SA需要将自己的TestAbilityStub实例通过AddSystemAbility接口注册到SystemAbilityManager,设备内与分布式的注册参数不同。

    // 注册到本设备内
    auto samgr = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager();
    samgr->AddSystemAbility(saId, new TestAbility());
    
    // 在组网场景下,会被同步到其他设备上
    auto samgr = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager();
    ISystemAbilityManager::SAExtraProp saExtra;
    saExtra.isDistributed = true; // 设置为分布式SA
    int result = samgr->AddSystemAbility(saId, new TestAbility(), saExtra);
    
  7. SA获取与调用

    通过SystemAbilityManager的GetSystemAbility方法可获取到对应SA的代理IRemoteObject,然后构造TestAbilityProxy即可。

    // 获取本设备内注册的SA的proxy
    sptr<ISystemAbilityManager> samgr = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager();
    sptr<IRemoteObject> remoteObject = samgr->GetSystemAbility(saId);
    sptr<ITestAbility> testAbility = iface_cast<ITestAbility>(remoteObject); // 使用iface_cast宏转换成具体类型
    
    // 获取其他设备注册的SA的proxy
    sptr<ISystemAbilityManager> samgr = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager();
    
    // networkId是组网场景下对应设备的标识符,可以通过GetLocalNodeDeviceInfo获取
    sptr<IRemoteObject> remoteObject = samgr->GetSystemAbility(saId, networkId);
    sptr<TestAbilityProxy> proxy(new TestAbilityProxy(remoteObject)); // 直接构造具体Proxy
    

JS侧开发步骤

  1. 添加依赖

    import rpc from "[@ohos](/user/ohos).rpc"
    import featureAbility from "[@ohos](/user/ohos).ability.featureAbility"
    
  2. 绑定Ability

    首先,构造变量want,指定要绑定的Ability所在应用的包名、组件名,如果是跨设备的场景,还需要绑定目标设备NetworkId(组网场景下对应设备的标识符,可以使用deviceManager获取目标设备的NetworkId);然后,构造变量connect,指定绑定成功、绑定失败、断开连接时的回调函数;最后,使用featureAbility提供的接口绑定Ability。

    import rpc from "[@ohos](/user/ohos).rpc"
    import featureAbility from "[@ohos](/user/ohos).ability.featureAbility"
    
    let proxy = null
    let connectId = null
    
    // 单个设备绑定Ability
    let want = {
        // 包名和组件名写实际的值
        "bundleName": "ohos.rpc.test.server",
        "abilityName": "ohos.rpc.test.server.ServiceAbility",
    }
    let connect = {
        onConnect: function(elementName, remote) {
            proxy = remote
        },
        onDisconnect: function(elementName) {
        },
        onFailed: function() {
            proxy = null
        }
    }
    connectId = featureAbility.connectAbility(want, connect)
    
    // 如果是跨设备绑定,可以使用deviceManager获取目标设备NetworkId
    import deviceManager from '[@ohos](/user/ohos).distributedHardware.deviceManager'
    function deviceManagerCallback(deviceManager) {
        let deviceList = deviceManager.getTrustedDeviceListSync()
        let networkId = deviceList[0].networkId
        let want = {
            "bundleName": "ohos.rpc.test.server",
            "abilityName": "ohos.rpc.test.service.ServiceAbility",
            "networkId": networkId,
            "flags": 256
        }
        connectId = featureAbility.connectAbility(want, connect)
    }
    // 第一个参数是本应用的包名,第二个参数是接收deviceManager的回调函数
    deviceManager.createDeviceManager("ohos.rpc.test", deviceManagerCallback)
    
  3. 服务端处理客户端请求

    服务端被绑定的Ability在onConnect方法里返回继承自rpc.RemoteObject的对象,该对象需要实现onRemoteMessageRequest方法,处理客户端的请求。

    onConnect(want: Want) {
        var robj:rpc.RemoteObject = new Stub("rpcTestAbility")
        return robj
    }
    class Stub extends rpc.RemoteObject {
        constructor(descriptor) {
            super(descriptor)
        }
        onRemoteMessageRequest(code, data, reply, option) {
            // 根据code处理客户端的请求
            return true
        }
    }
    
  4. 客户端处理服务端响应

    客户端在onConnect回调里接收到代理对象,调用sendRequestAsync方法发起请求,在期约(JavaScript期约:用于表示一个异步操作的最终完成或失败及其结果值)或者回调函数里接收结果。

    // 使用期约
    let option = new rpc.MessageOption()
    let data = rpc.MessageParcel.create()
    let reply = rpc.MessageParcel.create()
    // 往data里写入参数
    proxy.sendRequestAsync(1, data, reply, option)
        .then(function(result) {
            if (result.errCode != 0) {
                console.error("send request failed, errCode: " + result.errCode)
                return
            }
            // 从result.reply里读取结果
        })
        .catch(function(e) {
            console.error("send request got exception: " + e)
        }
        .finally(() => {
            data.reclaim()
            reply.reclaim()
        })
    
    // 使用回调函数
    function sendRequestCallback(result) {
        try {
            if (result.errCode != 0) {
                console.error("send request failed, errCode: " + result.errCode)
                return
            }
            // 从result.reply里读取结果
        } finally {
            result.data.reclaim()
            result.reply.reclaim()
        }
    }
    let option = new rpc.MessageOption()
    let data = rpc.MessageParcel.create()
    let reply = rpc.MessageParcel.create()
    // 往data里写入参数
    proxy.sendRequest(1, data, reply, option, sendRequestCallback)
    
  5. 断开连接

    IPC通信结束后,使用featureAbility的接口断开连接。

    import rpc from "[@ohos](/user/ohos).rpc"
    import featureAbility from "[@ohos](/user/ohos).ability.featureAbility"
    function disconnectCallback() {
        console.info("disconnect ability done")
    }
    featureAbility.disconnectAbility(connectId, disconnectCallback)
    

更多关于HarmonyOS 鸿蒙Next跨进程通信—IPC与RPC通信开发的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html

1 回复

更多关于HarmonyOS 鸿蒙Next跨进程通信—IPC与RPC通信开发的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


HarmonyOS 鸿蒙Next中的跨进程通信(IPC)与远程过程调用(RPC)是实现应用间或服务间数据交换和功能调用的关键机制。

IPC(Inter-Process Communication)在鸿蒙系统中,通常通过特定的通信通道实现不同进程间的数据传递和控制指令的发送。鸿蒙系统提供了多种IPC机制,如消息队列、管道、共享内存等,开发者可以根据具体需求选择合适的机制进行实现。

RPC(Remote Procedure Call)则是一种通过网络从远程计算机程序上请求服务的协议。在鸿蒙系统中,RPC允许一个进程调用另一个地址空间中(通常是不同机器上)的进程或函数,就像调用本地服务一样。鸿蒙系统的RPC框架通常负责处理底层通信细节,如序列化、反序列化、网络传输等,使得开发者可以更加专注于业务逻辑的实现。

在鸿蒙Next中进行IPC与RPC通信开发时,开发者需要熟悉鸿蒙系统的通信框架和API,以及相关的安全机制和数据传输规范。通过合理利用鸿蒙系统提供的通信机制,开发者可以实现高效、安全的应用间或服务间的数据交换和功能调用。

如果问题依旧没法解决请联系官网客服,官网地址是:https://www.itying.com/category-93-b0.html

回到顶部