鸿蒙HarmonyOS实战:IPC与RPC设备内进程通信

基本

IPC(Inter-Process Communication)与RPC(Remote Procedure Call)用于实现跨进程通信,不同的是前者使用Binder驱动,用于设备内的跨进程通信,后者使用软总线驱动,用于跨设备跨进程通信。需要跨进程通信的原因是因为每个进程都有自己独立的资源和内存空间,其他进程不能随意访问不同进程的内存和资源,IPC/RPC便是为了突破这一点。

说明

Stage模型不能直接使用本文介绍的IPC和RPC,需要通过以下能力实现相关业务场景:

  • IPC典型使用场景在后台服务,应用的后台服务通过IPC机制提供跨进程的服务调用能力。
  • RPC典型使用场景在多端协同,多端协同通过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对象不能在本设备内进行二次跨进程传递。

场景介绍

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

开发步骤

ArkTS侧开发步骤

  • 此文档中的示例代码描述的是系统应用跨进程通信。

  • 当前不支持三方应用实现ServiceExtensionAbility,三方应用的UIAbility组件可以通过Context连接系统提供的ServiceExtensionAbility。

  • 当前使用场景: 仅限客户端是三方应用,服务端是系统应用。

  1. 添加依赖

    复制代码
     // FA模型需要从@kit.AbilityKit导入featureAbility
     // import { featureAbility } from '@kit.AbilityKit';
     import { rpc } from '@kit.IPCKit';
  2. 绑定Ability

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

    复制代码
     // FA模型需要从@kit.AbilityKit导入featureAbility
     // import { featureAbility } from "@kit.AbilityKit";
     import { Want, common } from '@kit.AbilityKit';
     import { rpc } from '@kit.IPCKit';
     import { hilog } from '@kit.PerformanceAnalysisKit';
     import { distributedDeviceManager } from '@kit.DistributedServiceKit';
     import { BusinessError } from '@kit.BasicServicesKit';
    
     let dmInstance: distributedDeviceManager.DeviceManager | undefined;
     let proxy: rpc.IRemoteObject | undefined;
     let connectId: number;
    
     // 单个设备绑定Ability
     let want: Want = {
       // 包名和组件名写实际的值
       bundleName: "ohos.rpc.test.server",
       abilityName: "ohos.rpc.test.server.ServiceAbility",
     };
     let connect: common.ConnectOptions = {
       onConnect: (elementName, remoteProxy) => {
         hilog.info(0x0000, 'testTag', 'RpcClient: js onConnect called');
         proxy = remoteProxy;
       },
       onDisconnect: (elementName) => {
         hilog.info(0x0000, 'testTag', 'RpcClient: onDisconnect');
       },
       onFailed: () => {
         hilog.info(0x0000, 'testTag', 'RpcClient: onFailed');
       }
     };
     // FA模型使用此方法连接服务
     // connectId = featureAbility.connectAbility(want, connect);
    
     let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; // UIAbilityContext
     // 建立连接后返回的Id需要保存下来,在解绑服务时需要作为参数传入
     connectId = context.connectServiceExtensionAbility(want,connect);
    
     // 跨设备绑定
     try{
       dmInstance = distributedDeviceManager.createDeviceManager("ohos.rpc.test");
     } catch(error) {
       let err: BusinessError = error as BusinessError;
       hilog.error(0x0000, 'testTag', 'createDeviceManager errCode:' + err.code + ', errMessage:' + err.message);
     }
    
     // 使用distributedDeviceManager获取目标设备NetworkId
     if (dmInstance != undefined) {
       let deviceList = dmInstance.getAvailableDeviceListSync();
       let networkId = deviceList[0].networkId;
       let want: Want = {
         bundleName: "ohos.rpc.test.server",
         abilityName: "ohos.rpc.test.service.ServiceAbility",
         deviceId: networkId,
         flags: 256
       };
       // 建立连接后返回的Id需要保存下来,在断开连接时需要作为参数传入
       // FA模型使用此方法连接服务
       // connectId = featureAbility.connectAbility(want, connect);
    
       // 第一个参数是本应用的包名,第二个参数是接收distributedDeviceManager的回调函数
       connectId = context.connectServiceExtensionAbility(want,connect);
     }
  3. 服务端处理客户端请求

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

    复制代码
     import { rpc } from '@kit.IPCKit';
     import { Want } from '@kit.AbilityKit';
     class Stub extends rpc.RemoteObject {
       constructor(descriptor: string) {
         super(descriptor);
       }
       onRemoteMessageRequest(code: number, data: rpc.MessageSequence, reply: rpc.MessageSequence, option: rpc.MessageOption): boolean | Promise<boolean> {
         // 根据code处理客户端的请求
         return true;
       }
    
       onConnect(want: Want) {
         const robj: rpc.RemoteObject = new Stub("rpcTestAbility");
         return robj;
       }
     }
  4. 客户端处理服务端响应

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

    复制代码
     import { rpc } from '@kit.IPCKit';
     import { hilog } from '@kit.PerformanceAnalysisKit';
    
     // 使用期约
     let option = new rpc.MessageOption();
     let data = rpc.MessageSequence.create();
     let reply = rpc.MessageSequence.create();
     // 往data里写入参数
     let proxy: rpc.IRemoteObject | undefined;
     if (proxy != undefined) {
       proxy.sendMessageRequest(1, data, reply, option)
         .then((result: rpc.RequestResult) => {
           if (result.errCode != 0) {
             hilog.error(0x0000, 'testTag', 'sendMessageRequest failed, errCode: ' + result.errCode);
             return;
           }
           // 从result.reply里读取结果
         })
         .catch((e: Error) => {
           hilog.error(0x0000, 'testTag', 'sendMessageRequest got exception: ' + e);
         })
         .finally(() => {
           data.reclaim();
           reply.reclaim();
         })
     }
    
     // 使用回调函数
     function sendRequestCallback(err: Error, result: rpc.RequestResult) {
       try {
         if (result.errCode != 0) {
           hilog.error(0x0000, 'testTag', 'sendMessageRequest failed, errCode: ' + result.errCode);
           return;
         }
         // 从result.reply里读取结果
       } finally {
           result.data.reclaim();
           result.reply.reclaim();
       }
     }
     let options = new rpc.MessageOption();
     let datas = rpc.MessageSequence.create();
     let replys = rpc.MessageSequence.create();
     // 往data里写入参数
     if (proxy != undefined) {
       proxy.sendMessageRequest(1, datas, replys, options, sendRequestCallback);
     }
  5. 断开连接

    IPC通信结束后,FA模型使用featureAbility的接口断开连接,Stage模型在获取context后用提供的接口断开连接。

    复制代码
     // FA模型需要从@kit.AbilityKit导入featureAbility
     // import { featureAbility } from "@kit.AbilityKit";
     import { Want, common } from '@kit.AbilityKit';
     import { rpc } from '@kit.IPCKit';
     import { hilog } from '@kit.PerformanceAnalysisKit';
    
     function disconnectCallback() {
       hilog.info(0x0000, 'testTag', 'disconnect ability done');
     }
     // FA模型使用此方法断开连接
     // featureAbility.disconnectAbility(connectId, disconnectCallback);
    
     let proxy: rpc.IRemoteObject | undefined;
     let connectId: number;
    
     // 单个设备绑定Ability
     let want: Want = {
       // 包名和组件名写实际的值
       bundleName: "ohos.rpc.test.server",
       abilityName: "ohos.rpc.test.server.ServiceAbility",
     };
     let connect: common.ConnectOptions = {
       onConnect: (elementName, remote) => {
         proxy = remote;
       },
       onDisconnect: (elementName) => {
       },
       onFailed: () => {
         proxy;
       }
     };
     // FA模型使用此方法连接服务
     // connectId = featureAbility.connectAbility(want, connect);
    
     connectId = this.context.connectServiceExtensionAbility(want,connect);
    
     this.context.disconnectServiceExtensionAbility(connectId);

远端状态订阅开发实例

IPC/RPC提供对远端Stub对象状态的订阅机制,在远端Stub对象消亡时,可触发消亡通知告诉本地Proxy对象。这种状态通知订阅需要调用特定接口完成,当不再需要订阅时也需要调用特定接口取消。使用这种订阅机制的用户,需要实现消亡通知接口DeathRecipient并实现onRemoteDied方法清理资源。该方法会在远端Stub对象所在进程消亡或所在设备离开组网时被回调。值得注意的是,调用这些接口有一定的顺序。首先,需要Proxy订阅Stub消亡通知,若在订阅期间Stub状态正常,则在不再需要时取消订阅;若在订阅期间Stub所在进程退出或者所在设备退出组网,则会自动触发Proxy自定义的后续操作。

使用场景

这种订阅机制适用于本地Proxy对象需要感知远端Stub对象所在进程消亡,或所在设备离开组网的场景。当Proxy感知到Stub端消亡后,可适当清理本地资源。此外,RPC目前不提供匿名Stub对象的消亡通知,即只有向SAMgr注册过的服务才能被订阅消亡通知,IPC则支持匿名对象的消亡通知。

ArkTS侧接口

  • 此文档中的示例代码描述的是系统应用跨进程通信。

  • 当前不支持三方应用实现ServiceExtensionAbility,三方应用的UIAbility组件可以通过Context连接系统提供的ServiceExtensionAbility。

  • 当前使用场景: 仅限客户端是三方应用,服务端是系统应用。

接口名 返回值类型 功能描述
registerDeathRecipient void 注册用于接收远程对象消亡通知的回调,增加 proxy 对象上的消亡通知。
unregisterDeathRecipient void 注销用于接收远程对象消亡通知的回调。
onRemoteDied void 在成功添加死亡通知订阅后,当远端对象死亡时,将自动调用本方法。

参考代码

复制代码
// FA模型需要从@kit.AbilityKit导入featureAbility
// import { featureAbility } from '@kit.AbilityKit';
import { Want, common } from '@kit.AbilityKit';
import { rpc } from '@kit.IPCKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

let proxy: rpc.IRemoteObject | undefined;
let connect: common.ConnectOptions = {
  onConnect: (elementName, remoteProxy) => {
    hilog.info(0x0000, 'testTag', 'RpcClient: js onConnect called.');
    proxy = remoteProxy;
  },
  onDisconnect: (elementName) => {
    hilog.info(0x0000, 'testTag', 'RpcClient: onDisconnect');
  },
  onFailed: () => {
    hilog.info(0x0000, 'testTag', 'RpcClient: onFailed');
  }
};
let want: Want = {
  bundleName: "com.ohos.server",
  abilityName: "com.ohos.server.EntryAbility",
};
// FA模型通过此方法连接服务
// FA.connectAbility(want, connect);

// 建立连接后返回的Id需要保存下来,在解绑服务时需要作为参数传入
let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; // UIAbilityContext
// 建立连接后返回的Id需要保存下来,在解绑服务时需要作为参数传入
let connectionId = context.connectServiceExtensionAbility(want, connect);

上述onConnect回调函数中的proxy对象需要等ability异步连接成功后才会被赋值,然后才可调用proxy对象的unregisterDeathRecipient接口方法注销死亡回调

复制代码
import { rpc } from '@kit.IPCKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

class MyDeathRecipient implements rpc.DeathRecipient{
  onRemoteDied() {
    hilog.info(0x0000, 'testTag', 'server died');
  }
}
let deathRecipient = new MyDeathRecipient();
if (proxy != undefined) {
  proxy.registerDeathRecipient(deathRecipient, 0);
  proxy.unregisterDeathRecipient(deathRecipient, 0);
}

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指向的时候,内核会自动回收。

参考代码

复制代码
// 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<IRemoteObject> 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;
}

最后

小编在之前的鸿蒙系统扫盲中,有很多朋友给我留言,不同的角度的问了一些问题,我明显感觉到一点,那就是许多人参与鸿蒙开发,但是又不知道从哪里下手,因为资料太多,太杂,教授的人也多,无从选择。有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)资料用来跟着学习是非常有必要的。

为了确保高效学习,建议规划清晰的学习路线,涵盖以下关键阶段:


鸿蒙(HarmonyOS NEXT)最新学习路线

该路线图包含基础技能、就业必备技能、多媒体技术、六大电商APP、进阶高级技能、实战就业级设备开发,不仅补充了华为官网未涉及的解决方案

路线图适合人群:

IT开发人员: 想要拓展职业边界
零基础小白: 鸿蒙爱好者,希望从0到1学习,增加一项技能。
**技术提升/进阶跳槽:**发展瓶颈期,提升职场竞争力,快速掌握鸿蒙技术

2.视频学习资料+学习PDF文档

这份鸿蒙(HarmonyOS NEXT)资料包含了鸿蒙开发必掌握的核心知识要点,内容包含了**(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、(南向驱动、嵌入式等)鸿蒙项目实战等等)鸿蒙(HarmonyOS NEXT)**技术知识点。

HarmonyOS Next 最新全套视频教程

纯血版鸿蒙全套学习资料(面试、文档、全套视频等)

​​

总结

参与鸿蒙开发,你要先认清适合你的方向,如果是想从事鸿蒙应用开发方向的话,可以参考本文的学习路径,简单来说就是:为了确保高效学习,建议规划清晰的学习路线

相关推荐
腾讯TNTWeb前端团队4 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰7 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪7 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪7 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy8 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom8 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom9 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom9 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom9 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom9 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试