diff --git a/zh-cn/application-dev/connectivity/ipc-rpc-development-guideline.md b/zh-cn/application-dev/connectivity/ipc-rpc-development-guideline.md index 5f6376ab378a99210efe64af24a200e2f72d04af..4bfaf67a20f1e4cb1e44fd81c259c609fb4a5cda 100755 --- a/zh-cn/application-dev/connectivity/ipc-rpc-development-guideline.md +++ b/zh-cn/application-dev/connectivity/ipc-rpc-development-guideline.md @@ -9,18 +9,39 @@ IPC/RPC的主要工作是让运行在不同进程的Proxy和Stub互相通信, **表1** Native侧IPC接口 -| 类/接口 | 方法 | 功能说明 | +| 类/接口 | 方法 | 功能说明 | | -------- | -------- | -------- | -| [IRemoteBroker](../reference/apis/js-apis-rpc.md#iremotebroker) | sptr<IRemoteObject> AsObject() | 返回通信对象。派生类需要实现,Stub端返回RemoteObject对象本身,Proxy端返回代理对象。 | -| IRemoteStub | virtual int OnRemoteRequest(uint32_t code, MessageParcel &data, MessageParcel &reply, MessageOption &option) | 请求处理方法,派生类需要重写该方法用来处理Proxy的请求并返回结果。 | -| IRemoteProxy | | 业务Proxy类,派生自IRemoteProxy类。 | +| [IRemoteBroker](../reference/apis/js-apis-rpc.md#iremotebroker) | sptr<IRemoteObject> AsObject() | 返回通信对象。派生类需要实现,Stub端返回RemoteObject对象本身,Proxy端返回代理对象。 | +| IRemoteStub | virtual int OnRemoteRequest(uint32_t code, MessageParcel &data, MessageParcel &reply, MessageOption &option) | 请求处理方法,派生类需要重写该方法用来处理Proxy的请求并返回结果。 | +| IRemoteProxy | | 业务Proxy类,派生自IRemoteProxy类。 | ## 开发步骤 **Native侧开发步骤** -1. 定义IPC接口ITestAbility +1.添加依赖 + +sdk依赖: + +``` +external_deps = [ + "ipc:ipc_core", +] +``` + +此外, IPC/RPC依赖的refbase实现在公共基础库下,请增加对utils的依赖: + +``` +deps = [ + “//utils/native/base:utils” +] +include_dirs = [ +“//utils/native/base/include”, +] +``` + +2. 定义IPC接口ITestAbility SA接口继承IPC基类接口IRemoteBroker,接口里定义描述符、业务函数和消息码,其中业务函数在Proxy端和Stub端都需要实现。 @@ -34,18 +55,18 @@ IPC/RPC的主要工作是让运行在不同进程的Proxy和Stub互相通信, }; ``` -2. 定义和实现服务端TestAbilityStub +3. 定义和实现服务端TestAbilityStub 该类是和IPC框架相关的实现,需要继承 IRemoteStub<ITestAbility>。Stub端作为接收请求的一端,需重写OnRemoteRequest方法用于接收客户端调用。 ``` class TestAbilityStub : public IRemoteStub { - public: + public: virtual int OnRemoteRequest(uint32_t code, MessageParcel &data, MessageParcel &reply, MessageOption &option) override; int TestPingAbility(const std::u16string &dummy) override; }; - int TestServiceStub::OnRemoteRequest(uint32_t code, + int TestAbilityStub::OnRemoteRequest(uint32_t code, MessageParcel &data, MessageParcel &reply, MessageOption &option) { switch (code) { @@ -61,7 +82,7 @@ IPC/RPC的主要工作是让运行在不同进程的Proxy和Stub互相通信, } ``` -3. 定义服务端业务函数具体实现类TestAbility +4. 定义服务端业务函数具体实现类TestAbility ``` class TestAbility : public TestAbilityStub { public: @@ -73,7 +94,7 @@ IPC/RPC的主要工作是让运行在不同进程的Proxy和Stub互相通信, } ``` -4. 定义和实现客户端 TestAbilityProxy +5. 定义和实现客户端 TestAbilityProxy 该类是Proxy端实现,继承IRemoteProxy<ITestAbility>,调用SendRequest接口向Stub端发送请求,对外暴露服务端提供的能力。 @@ -100,8 +121,8 @@ IPC/RPC的主要工作是让运行在不同进程的Proxy和Stub互相通信, return result; } ``` - -5. SA注册与启动 + +6. SA注册与启动 SA需要将自己的TestAbilityStub实例通过AddSystemAbility接口注册到SystemAbilityManager,设备内与分布式的注册参数不同。 @@ -109,7 +130,7 @@ IPC/RPC的主要工作是让运行在不同进程的Proxy和Stub互相通信, // 注册到本设备内 auto samgr = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager(); samgr->AddSystemAbility(saId, new TestAbility()); - + // 在组网场景下,会被同步到其他设备上 auto samgr = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager(); ISystemAbilityManager::SAExtraProp saExtra; @@ -117,7 +138,7 @@ IPC/RPC的主要工作是让运行在不同进程的Proxy和Stub互相通信, int result = samgr->AddSystemAbility(saId, new TestAbility(), saExtra); ``` -6. SA获取与调用 +7. SA获取与调用 通过SystemAbilityManager的GetSystemAbility方法可获取到对应SA的代理IRemoteObject,然后构造TestAbilityProxy即可。 @@ -133,3 +154,138 @@ IPC/RPC的主要工作是让运行在不同进程的Proxy和Stub互相通信, sptr proxy(new TestAbilityProxy(remoteObject)); // 直接构造具体Proxy ``` +**JS侧开发步骤** + +1. 添加依赖 + +``` +import rpc from "@ohos.rpc" +import featureAbility from "@ohos.ability.featureAbility" +] +``` + +2. 客户端构造变量want,指定要绑定的Ability所在应用的包名、组件名,如果是跨设备的场景,还需要目标设备NetworkId。构造变量connect,指定绑定成功、绑定失败、断开连接时的回调函数。使用featureAbility提供的接口绑定Ability。 + + ``` + import rpc from "@ohos.rpc" + import featureAbility from "@ohos.ability.featureAbility" + + let proxy = null + let connectId = null + + // 单个设备 + 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.distributedHardware.deviceManager' + function deviceManagerCallback(deviceManager) { + let deviceList = deviceManager.getTrustedDeviceListSync() + let deviceId = deviceList[0].deviceId + let want = { + "bundleName": "ohos.rpc.test.server", + "abilityName": "ohos.rpc.test.service.ServiceAbility", + "deviceId": deviceId, + "flags": 256 + } + connectId = featureAbility.connectAbility(want, connect) + } + // 第一个参数是本应用的包名,第二个参数是接收deviceManager的回调函数 + deviceManager.createDeviceManager("ohos.rpc.test", deviceManagerCallback) + ``` + + + +3. 服务端被绑定的Ability在onConnect方法里返回继承自rpc.RemoteObject的对象,该对象需要实现onRemoteMessageRequest方法,处理客户端的请求。 + + ``` + import rpc from "@ohos.rpc" + 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方法发起请求,在期约或者回调函数里接收结果。 + + ``` + import rpc from "@ohos.rpc" + // 使用期约 + 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.rpc" + import featureAbility from "@ohos.ability.featureAbility" + function disconnectCallback() { + console.info("disconnect ability done") + } + featureAbility.disconnectAbility(connectId, disconnectCallback) + ``` + diff --git a/zh-cn/application-dev/connectivity/ipc-rpc-overview.md b/zh-cn/application-dev/connectivity/ipc-rpc-overview.md index ff1261dc0a27b968ba19d86f7bddc5fb401131a7..f081a9ca13f54a705f51579440bb8e3cf2db7366 100755 --- a/zh-cn/application-dev/connectivity/ipc-rpc-overview.md +++ b/zh-cn/application-dev/connectivity/ipc-rpc-overview.md @@ -3,13 +3,29 @@ ## 基本概念 -IPC(Inter-Process Communication)与RPC(Remote Procedure Call)机制用于实现跨进程通信,不同的是前者使用Binder驱动,用于设备内的跨进程通信,而后者使用软总线驱动,用于跨设备跨进程通信。IPC和RPC通常采用客户端-服务器(Client-Server)模型,服务请求方(Client)可获取提供服务提供方(Server)的代理 (Proxy),并通过此代理读写数据来实现进程间的数据通信。通常,Server会先注册系统能力(System Ability)到系统能力管理者(System Ability Manager,缩写SAMgr)中,SAMgr负责管理这些SA并向Client提供相关的接口。Client要和某个具体的SA通信,必须先从SAMgr中获取该SA的代理,然后使用代理和SA通信。下文使用Proxy表示服务请求方,Stub表示服务提供方。 +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,过大的数据量请使用匿名共享内存。 -不支持把跨设备的Proxy对象传递回该Proxy对象所指向的Stub对象所在的设备。 +单个设备上跨进程通信时,传输的数据量最大约为1MB,过大的数据量请使用匿名共享内存。不支持的第一个场景是在RPC中订阅匿名Stub对象的死亡通知。 +不支持把跨设备的Proxy对象传递回该Proxy对象所指向的Stub对象所在的设备,即指向远端A侧Stub的Proxy对象不能在本设备内进行二次跨进程传递。 + +## 使用建议 + +首先,需要编写接口类,接口类中必须定义消息码,供通信双方标识操作,还可以有未实现的的方法,还需要实现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与远端通信 ## 相关模块 diff --git a/zh-cn/application-dev/connectivity/subscribe-remote-state.md b/zh-cn/application-dev/connectivity/subscribe-remote-state.md index 2dee9fc483feb421aebd011d7d5d6495202baf65..427227ddd32ad29148b1b4b992fe243d7529be29 100755 --- a/zh-cn/application-dev/connectivity/subscribe-remote-state.md +++ b/zh-cn/application-dev/connectivity/subscribe-remote-state.md @@ -2,19 +2,21 @@ IPC/RPC提供对远端Stub对象状态的订阅机制, 在远端Stub对象死亡时,可触发死亡通知告诉本地Proxy对象。这种状态通知订阅需要调用特定接口完成,当不再需要订阅时也需要调用特定接口取消。使用这种订阅机制的用户,需要实现死亡通知接口DeathRecipient并实现onRemoteDied方法清理资源。该方法会在远端Stub对象所在进程死亡或所在设备离开组网时被回调。值得注意的是,调用这些接口有一定的顺序。首先,需要Proxy订阅Stub死亡通知,若在订阅期间Stub状态正常,则在不再需要时取消订阅;若在订阅期间Stub所在进程退出或者所在设备退出组网,则会自动触发Proxy自定义的后续操作。 +## 使用场景 +这种订阅机制适用于本地Proxy对象需要感知远端Stub对象所在进程死亡,或所在设备离开组网的场景。当Proxy感知到Stub端死亡后,可适当清理本地资源。此外,RPC目前不提供匿名Stub对象的死亡通知,即只有向SAMgr注册过的服务才能被订阅死亡通知,IPC则支持匿名对象的死亡通知。 ## Native侧接口 -| 接口名 | 功能描述 | -| -------- | -------- | -| AddDeathRecipient(const sptr\ &recipient); | 订阅远端Stub对象状态。 | -| RemoveDeathRecipient(const sptr\ &recipient); | 取消订阅远端Stub对象状态。 | -| OnRemoteDied(const wptr\ &object); | 当远端Stub对象死亡时回调。 | +| 接口名 | 返回值类型 | 功能描述 | +| -------- | -------- | -------- | +| AddDeathRecipient(const sptr\ &recipient); | bool | 订阅远端Stub对象状态。 | +| RemoveDeathRecipient(const sptr\ &recipient); | bool | 取消订阅远端Stub对象状态。 | +| OnRemoteDied(const wptr\ &object); | void | 当远端Stub对象死亡时回调。 | -## 参考代码 +### 参考代码 ``` @@ -26,3 +28,50 @@ sptr deathRecipient (new TestDeathRecipient());// bool result = proxy->AddDeathRecipient(deathRecipient); // 注册死亡通知 result = proxy->RemoveDeathRecipient(deathRecipient); // 移除死亡通知 ``` + +## Stub感知Proxy死亡(匿名Stub的使用) + +正向的死亡通知是Proxy感知Stub的状态,若想达到反向的死亡通知,即Stub感知Proxy的状态,可以巧妙的利用正向死亡通知。如两个进程A(原Stub所在进程)和B(原Proxy所在进程),进程B在获取到进程A的Proxy对象后,在B进程新建一个匿名Stub对象(匿名指未向SAMgr注册),可称之为回调Stub,再通过SendRequest接口将回调Stub传给进程A的原Stub。这样一来,进程A便获取到了进程B的回调Proxy。当进程B死亡或B所在设备离开组网时,回调Stub会死亡,回调Proxy会感知,进而通知给原Stub,便实现了反向死亡通知。 + +注意: + +- 反向死亡通知仅限设备内跨进程通信使用,不可用于跨设备 +- 当匿名Stub对象没有被任何一个Proxy指向的时候,内核会自动回收 + +### Native侧参考代码 + +```c++ +//Proxy +int TestAbilityProxy::TestAnonymousStub() +{ + MessageOption option; + MessageParcel dataParcel, replyParcel; + dataParcel.UpdateDataVersion(Remote()); + dataParcel.WriteRemoteObject(new TestAbilityStub()); + int error = Remote()->SendRequest(TRANS_ID_REVERSED_MONITOR,dataParcel, replyParcel, option); + int result = (error == ERR_NONE) ? replyParcel.ReadInt32() : -1; + return result; +} + +//Stub + +int TestAbilityStub::OnRemoteRequest(uint32_t code, MessageParcel &data, MessageParcel &reply, MessageOption &option) +{ + switch (code) { + case TRANS_ID_REVERSED_MONITOR: { + sptr obj = data.ReadRemoteObject(); + if (obj == nullptr) { + reply.WriteInt32(ERR_NULL_OBJECT); + return ERR_NULL_OBJECT; + } + bool result = obj->AddDeathRecipient(new TestDeathRecipient()); + result ? reply.WriteInt32(ERR_NONE) : reply.WriteInt32(-1); + break; + } + default: + break; + } + return ERR_NONE; +} +``` +