一、重要性
OpenHarmony IPC 之 ServiceAbility 应用极其广泛。例如在 OpenHarmony 以下四个目录共找到约 1087 个子系统用到了这种机制,其重要性可见一斑。
grep -nr "::OnRemoteRequest" ./foundation/ ./base/ ./drivers/ ./kernel/
调试时 OnRemoteRequest()函数是必经之路,服务端函数有没有调起就在这里“打桩”或输出日志。
Client-Server 软件模型外加 Interface 接口共同组成了 OpenHarmony 诸多子系统的架构,使其系统的稳定性、扩展性以及进程通信能力得到加强。子系统中均可见到 Interface 接口目录、Framework 客户端目录以及 Service 服务端目录,工作重点一般集中在 Service 服务端,如下图所示。
这种软件架构提供了丰富的软件接口,又提供了灵活的接口组织策略,很容易将一个子系统与另一个子系统相连接,形成一个有机的整体。典型的通路是 Foundation→Base→Drivers(Third_paty)
传统紧凑的软件架构因扩展性、稳定性饱受诟病,与其形成了鲜明对比,如下图:
说明
以下部分内容来自参考链接,他们已经整理的很好了,此处是借花献佛!
二、IPC 介绍
IPC(Inter-Process Communication)
IPC 是用于进程间通信的技术,指的是进程间的数据交互过程。它包括各种形式的消息传递,共享资源,以及同步对象,如互斥量等,以确保安全的并发访问共享资源。IPC 通常使用 Binder 驱动,主要用于设备内的跨进程通信,如 OpenHarmony 系统中的进程间通信。
IPC 与 RPC(Remote Procedure Call)机制用于实现跨进程通信,不同的是前者使用 Binder 驱动,用于设备内的跨进程通信,而后者使用软总线驱动,用于跨设备跨进程通信。IPC 和 RPC 通常采用客户端-服务器(Client-Server)模型,服务请求方(Client)可获取提供服务提供方(Server)的代理 (Proxy),并通过此代理读写数据来实现进程间的数据通信。通常,系统能力(System Ability)Server 侧会先注册到系统能力管理者(System Ability Manager,缩写 SAMgr)中,SAMgr 负责管理这些 SA 并向 Client 提供相关的接口。Client 要和某个具体的 SA 通信,必须先从 SAMgr 中获取该 SA 的代理,然后使用代理和 SA 通信。三方应用可以使用 FA 提供的接口绑定服务提供方的 Ability,获取代理,进行通信。下文使用 Proxy 表示服务请求方,Stub 表示服务提供方。
总结来说,IPC 是进程间通信的方式,侧重于进程内的数据共享和资源管理。IPC 通信机制架构图如下:
IPC 代码目录
约束
- 单个设备上跨进程通信时,传输的数据量最大约为 1MB,过大的数据量请使用匿名共享内存。
- 不支持在 RPC 中订阅匿名 Stub 对象(没有向 SAMgr 注册 Stub 对象)的死亡通知。
- 不支持把跨设备的 Proxy 对象传递回该 Proxy 对象所指向的 Stub 对象所在的设备,即指向远端设备 Stub 的 Proxy 对象不能在本设备内进行二次跨进程传递。
三、ServiceAbility 应用
编译依赖
sdk 依赖:
external_deps = [
"ipc:ipc_core",
]
此外, IPC/RPC 依赖的 refbase 实现在公共基础库下,请增加对 utils 的依赖:
external_deps = [
"c_utils:utils",
]
说明
通过 SA 的标识和设备 NetworkId,从 SAMgr 获取 Proxy,通过 Proxy 实现与 Stub 的跨进程通信。
实现跨进程通信的基本步骤:
- 定义接口类
接口类继承 IRemoteBroker,定义描述符、业务函数和消息码。 - 实现服务提供端(Stub)
Stub 继承 IRemoteStub(Native),除了接口类中未实现方法外,还需要实现 AsObject 方法及 OnRemoteRequest 方法。 - 实现服务请求端(Proxy)
Proxy 继承 IRemoteProxy(Native),封装业务函数,调用 SendRequest 将请求发送到 Stub。 - 注册 SA
服务提供方所在进程启动后,申请 SA 的唯一标识,将 Stub 注册到 SAMgr。 - 获取 SA
通过 SA 的标识和设备 NetworkId,从 SAMgr 获取 Proxy,通过 Proxy 实现与 Stub 的跨进程通信。
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 类派生,该方法用来向对端发送消息。 |
使用说明
1. 定义 IPC 接口 ITestAbility
IPC 接口继承 IPC 基类接口 IRemoteBroker,接口里定义描述符、业务函数和消息码,其中业务函数在 Proxy 端和 Stub 端都需要实现。
class ITestAbility : public IRemoteBroker {
public:
// DECLARE_INTERFACE_DESCRIPTOR是必须的, 入参需使用std::u16string;
DECLARE_INTERFACE_DESCRIPTOR(u"test.ITestAbility"); // DESCRIPTOR接口描述符建议使用"组件名.类名"的格式
int TRANS_ID_PING_ABILITY = 1; // 定义消息码
virtual int TestPingAbility(const std::u16string &dummy) = 0; // 定义业务函数
};
2. 定义和实现服务端 TestAbilityStub
该类是和 IPC 框架相关的实现,需要继承自 IRemoteStub。Stub 端作为接收请求的一端,需重写 OnRemoteRequest 方法用于接收客户端调用。
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 TestServiceStub::OnRemoteRequest(uint32_t code,
MessageParcel &data, MessageParcel &reply, MessageOption &option)
{
if (data.ReadInterfaceToken() != GetDescriptor()) { //校验是否为本服务的接口描述符,避免中继攻击
return -1;
}
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);
}
}
3. 定义服务端业务函数具体实现类 TestAbility
class TestAbility : public TestAbilityStub {
public:
int TestPingAbility(const std::u16string &dummy);
}
int TestAbility::TestPingAbility(const std::u16string &dummy) {
return 0;
}
4. 定义和实现客户端 TestAbilityProxy
该类是 Proxy 端实现,继承自 IRemoteProxy,调用 SendRequest 接口向 Stub 端发送请求,对外暴露服务端提供的能力。
class TestAbilityProxy : public IRemoteProxy<ITestAbility> {
public:
explicit TestAbilityProxy(const sptr<IRemoteObject> &impl);
int TestPingService(const std::u16string &dummy) override;
private:
static inline BrokerDelegator<TestAbilityProxy> delegator_; // 方便使用iface_cast宏
}
TestAbilityProxy::TestAbilityProxy(const sptr<IRemoteObject> &impl)
: IRemoteProxy<ITestAbility>(impl)
{
}
int TestAbilityProxy::TestPingService(const std::u16string &dummy) {
MessageOption option;
MessageParcel dataParcel, replyParcel;
if(!dataParcel.WriteInterfaceToken(GetDescriptor())) { //所有对外接口的proxy实现都要写入接口描述符,用于stub端检验
return -1;
}
if(!dataParcel.WriteString16(dummy)) {
return -1;
}
int error = Remote()->SendRequest(TRANS_ID_PING_ABILITY, dataParcel, replyParcel, option);
int result = (error == ERR_NONE) ? replyParcel.ReadInt32() : -1;
return result;
}
5. 同步调用与异步调用
MessageOption 作为发送接口(原型如下)的入参,可设定同步(TF_SYNC)、异步(TF_ASYNC),默认情况下设定为同步,其余可通过 MessageOption 构造方法或 void SetFlags(int flags)设定。
int SendRequest(uint32_t code, MessageParcel &data,
MessageParcel &reply, MessageOption &option) override;
MessageOption option;
option.setFlags(option.TF_ASYNC);
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();
sptr<IRemoteObject> remoteObject = samgr->GetSystemAbility(sdid, deviceId); // deviceId是指定设备的标识符
sptr<TestAbilityProxy> proxy(new TestAbilityProxy(remoteObject)); // 直接构造具体Proxy
总是有很多小伙伴反馈说:OpenHarmony开发不知道学习哪些技术?不知道需要重点掌握哪些OpenHarmony开发知识点? 为了解决大家这些学习烦恼。在这准备了一份很实用的鸿蒙全栈开发学习路线与学习文档给大家用来跟着学习。
针对一些列因素,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线,包含了鸿蒙开发必掌握的核心知识要点,内容有(OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、OpenHarmony驱动开发、系统定制移植……等)技术知识点。
OpenHarmony 开发环境搭建:https://gitcode.com/HarmonyOS_MN/733GH/overview
《OpenHarmony源码解析》
搭建开发环境
系统架构分析
- 构建子系统
- 启动流程
- 子系统
- 分布式任务调度子系统
- 分布式通信子系统
- 驱动子系统
- ……