鸿蒙Service Ability的前世今生--基础篇
一、初识ServiceAbility
在OpenHarmony中基于framework层提供的服务都是基于Service Abiltiy实现的。Service Ability以下简称SA。SA在一个设备中只会存在一个实例的。开发者通过SA的机制可以实现跨进程的通信。
以下通过的例子方式说明如何使用OpenHarmony开源代码中提供现有SA。后续如果开发了自定义的SA,也可以通过此种方法对自定义的SA进行测试接口。
1.1 如何使用ServiceAbility
以下节选自OpenHarmony v3.2 Release版本。
// base\useriam\face_auth\services\src\face_auth_service.cppsptr FaceAuthService::GetBundleMgr(){ IAM_LOGI("start"); if (bundleMgr_ != nullptr) { return bundleMgr_; } auto sam = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager(); if (sam == nullptr) { IAM_LOGE("GetSystemAbilityManager return nullptr"); return nullptr; } auto bundleMgrSa = sam->GetSystemAbility(BUNDLE_MGR_SERVICE_SYS_ABILITY_ID); if (bundleMgrSa == nullptr) { IAM_LOGE("GetSystemAbility return nullptr"); return nullptr; } bundleMgr_ = iface_cast(bundleMgrSa); return bundleMgr_;}
-
首先通过单实例SystemAbilityManagerClient的方法GetSystemAbilityManager获取System ability的管理类实例sam。
-
然后通过SA定义相对应的ID号,获取远程的代理基类指针sptr。
-
通过类型转换成SA对应的代理类
-
此时即可通过bundleMgr_访问SA提供的方法。如bundleMgr->CheckIsSystemAppByUid(uid)
// base\useriam\face_auth\services\src\face_auth_service.cppint32_t FaceAuthService::SetBufferProducer(sptr &producer){... int32_t uid = IPCSkeleton::GetCallingUid(); auto bundleMgr = GetBundleMgr(); if (bundleMgr == nullptr) { IAM_LOGE("bundleMgr is nullptr"); return FACE_AUTH_ERROR; } if (!bundleMgr->CheckIsSystemAppByUid(uid)) { IAM_LOGE("the caller is not a system application"); return FACE_AUTH_CHECK_SYSTEM_PERMISSION_FAILED; } ... return FACE_AUTH_SUCCESS;}
1.2 SA的主要构成
一个SA主要由四个部份组成。
- 定义对外的IPC接口类
- 定义客户端通信代理proxy类
- 定义服务端通信stub类
- SA服务的实现类
以上四个类达成了跨进程通信与内容封装的整个过程,以下会通过OpenHarmony V3.2中的开源代码中的电话子系统其中的CoreService服务进行实例说明。
1.2.1 IPC接口类
// base\telephony\core_service\interfaces\innerkits\include\i_core_service.hclass ICoreService : public IRemoteBroker {public: DECLARE_INTERFACE_DESCRIPTOR(u"ohos.telephony.ICoreService");public: virtual ~ICoreService() = default; virtual int32_t GetPsRadioTech(int32_t slotId, int32_t &psRadioTech) = 0; virtual int32_t GetCsRadioTech(int32_t slotId, int32_t &csRadioTech) = 0;... enum class InterfaceID { GET_PS_RADIO_TECH = 0, GET_CS_RADIO_TECH,... };...};
- 首先定义IPC接口类ICoreService,继承OpenHarmony统一对外提供的接口类IRemoteBroker。
- 同时实现该IPC对外接口的唯一标识符,该标识符用于IPC通信的校验等目的。通过DECLARE_INTERFACE_DESCRIPTOR提供标识符的定义与获取标识符的对外接口GetDescriptor。
- 定义该服务对外提供的能力集合函数。本例中GetPsRadioTech、GetCsRadioTech等接口即是。
- 定义的枚举为接口的code码,该code码用于IPC通信中,proxy端与stub端进行接口识别。对外有几个接口就需要定义几个code码。
1.2.2 代理proxy类
// base\telephony\core_service\interfaces\innerkits\include\core_service_proxy.hclass CoreServiceProxy : public IRemoteProxy {public: explicit CoreServiceProxy(const sptr &impl) : IRemoteProxy(impl) {} virtual ~CoreServiceProxy() = default; int32_t GetPsRadioTech(int32_t slotId, int32_t &psRadioTech) override; int32_t GetCsRadioTech(int32_t slotId, int32_t &csRadioTech) override;...private: static inline BrokerDelegator delegator_;};
- 首先继承IRemoteProxy,如上所示
- 定义私有静态内联成员BrokerDelegator delegator_;
- 实现接口中的虚函数GetPsRadioTech、GetCsRadioTech,如下所示,通过MessageParcel对参数进行序列化操作,然后通过remote->SendRequest将接口对应的code码与对应的序列化参数传送至stub的实现类
// base\telephony\core_service\frameworks\native\src\core_service_proxy.cppint32_t CoreServiceProxy::GetPsRadioTech(int32_t slotId, int32_t &psRadioTech){ MessageParcel data; MessageParcel reply; MessageOption option; if (!WriteInterfaceToken(data)) { TELEPHONY_LOGE("GetPsRadioTech WriteInterfaceToken is false"); return TELEPHONY_ERR_WRITE_DESCRIPTOR_TOKEN_FAIL; } data.WriteInt32(slotId); auto remote = Remote(); if (remote == nullptr) { TELEPHONY_LOGE("GetPsRadioTech Remote is null"); return TELEPHONY_ERR_IPC_CONNECT_STUB_FAIL; } int32_t st = remote->SendRequest(uint32_t(InterfaceID::GET_PS_RADIO_TECH), data, reply, option); if (st != ERR_NONE) { TELEPHONY_LOGE("GetPsRadioTech failed, error code is %{public}d ", st); return TELEPHONY_ERR_IPC_CONNECT_STUB_FAIL; } int32_t result = reply.ReadInt32(); if (result == TELEPHONY_ERR_SUCCESS) { psRadioTech = reply.ReadInt32(); } return result;}
1.2.3 stub类
// base\telephony\core_service\services\core\include\core_service_stub.hclass CoreServiceStub : public IRemoteStub {public: CoreServiceStub(); virtual ~CoreServiceStub() {} int32_t OnRemoteRequest(uint32_t code, MessageParcel &data, MessageParcel &reply, MessageOption &option) override;...private: using CoreServiceFunc = int32_t (CoreServiceStub::*)(MessageParcel &data, MessageParcel &reply); int32_t OnGetPsRadioTech(MessageParcel &data, MessageParcel &reply); int32_t OnGetCsRadioTech(MessageParcel &data, MessageParcel &reply);...private: std::map memberFuncMap_;};
- 服务端stub类继承IRemoteStub,实现虚函数OnRemoteRequest完成接口code码与对应处理函数的对应
// base\telephony\core_service\services\core\src\core_service_stub.cppint32_t CoreServiceStub::OnRemoteRequest( uint32_t code, MessageParcel &data, MessageParcel &reply, MessageOption &option){ TELEPHONY_LOGI("CoreServiceStub OnRemoteRequest code %{public}u", code); std::u16string myDescripter = CoreServiceStub::GetDescriptor(); std::u16string remoteDescripter = data.ReadInterfaceToken(); if (myDescripter != remoteDescripter) { TELEPHONY_LOGE("descriptor checked fail"); return TELEPHONY_ERR_DESCRIPTOR_MISMATCH; } auto itFunc = memberFuncMap_.find(code); if (itFunc != memberFuncMap_.end()) { auto memberFunc = itFunc->second; if (memberFunc != nullptr) { return (this->*memberFunc)(data, reply); } } return IPCObjectStub::OnRemoteRequest(code, data, reply, option);}
- code:IPC接口类定义的接口定义码,标识服务端的能力
- data:表示proxy传递到服务端序列化之后的参数列表
- reply:表示此次proxy的IPC请求,服务端响应服务传回proxy的应答序列化参数列表
- option:表示此次IPC通信的通信方式,SYNC同步或ASYCN异步,默认为SYNC。同步表示客户端IPC跨进程调用会阻塞客户端,直至服务端处理完此次业务逻辑才返回,异步方式为客户端proxy发起IPC通信后即返回,不需要等待服务端响应。
- 构造时通过map映射接口定义code与stub的处理函数的对应
// base\telephony\core_service\services\core\src\core_service_stub.cppvoid CoreServiceStub::AddHandlerNetWorkToMap(){ memberFuncMap_[uint32_t(InterfaceID::GET_PS_RADIO_TECH)] = &CoreServiceStub::OnGetPsRadioTech; memberFuncMap_[uint32_t(InterfaceID::GET_CS_RADIO_TECH)] = &CoreServiceStub::OnGetCsRadioTech;...}
- 实现OnGetPsRadioTech等响应函数,对data进行反序列化得到入参列表,调用服务实现业务逻辑,将返回值序列化后返回结果
// base\telephony\core_service\services\core\src\core_service_stub.cppint32_t CoreServiceStub::OnGetPsRadioTech(MessageParcel &data, MessageParcel &reply){ auto slotId = data.ReadInt32(); int32_t radioTech = 0; int32_t result = GetPsRadioTech(slotId, radioTech); reply.WriteInt32(result); if (result == TELEPHONY_ERR_SUCCESS) { reply.WriteInt32(radioTech); } return result;}
1.2.4 SA服务的实现类
// base\telephony\core_service\services\core\include\core_service.hclass CoreService : public SystemAbility, public CoreServiceStub { DECLARE_DELAYED_SINGLETON(CoreService) DECLARE_SYSTEM_ABILITY(CoreService)public: void OnStart() override; void OnStop() override; int32_t GetPsRadioTech(int32_t slotId, int32_t &psRadioTech) override; int32_t GetCsRadioTech(int32_t slotId, int32_t &csRadioTech) override;...}
-
服务类需继承safwk的SystemAbility以及1.2.3提及的服务端stub类CoreServiceStub
由于CoreServiceStub继承自ICoreService,但是只是实现了OnRemoteRequest的虚函数,像GetPsRadioTech、GetCsRadioTech等虚函数未实现,CoreServiceStub仍然只是抽象类,不能实例化对象。因此实例化的操作就交由CoreService来完成,即由CoreService实现GetPsRadioTech、GetCsRadioTech等虚函数。
// base\telephony\core_service\services\core\src\core_service.cppint32_t CoreService::GetPsRadioTech(int32_t slotId, int32_t &psRadioTech){...}int32_t CoreService::GetCsRadioTech(int32_t slotId, int32_t &csRadioTech){...}
- 需要调用safwk提供的宏REGISTER_SYSTEM_ABILITY_BY_ID对该SA进行注册。
REGISTER_SYSTEM_ABILITY_BY_ID(CoreService, TELEPHONY_CORE_SERVICE_SYS_ABILITY_ID, true);
此宏定义如下:
// foundation\systemabilitymgr\safwk\services\safwk\include\system_ability.h#define REGISTER_SYSTEM_ABILITY_BY_ID(abilityClassName, systemAbilityId, runOnCreate) \ const bool abilityClassName##_##RegisterResult = \ SystemAbility::MakeAndRegisterAbility(new abilityClassName(systemAbilityId, runOnCreate));
故在实例中,CoreService采用直接使用MakeAndRegisterAbility的方式
// base\telephony\core_service\services\core\src\core_service.cppSystemAbility::MakeAndRegisterAbility(DelayedSingleton::GetInstance().get());
- 在REGISTER_SYSTEM_ABILITY_BY_ID中,第一个参数代表ability的类名,第二个参数为当前SA的serviceId,此serviceId由子系统进行分配,每个子系统大约有100个左右的serviceId,一般定义在foundation\systemabilitymgr\samgr\interfaces\innerkits\samgr_proxy\include\system_ability_definition.h中。本例中
// foundation\systemabilitymgr\samgr\interfaces\innerkits\samgr_proxy\include\system_ability_definition.hTELEPHONY_CORE_SERVICE_SYS_ABILITY_ID = 4010,
第三个参数runOnCreate,该参数标识SA是否随进程而启动。true表示进程启动时拉起该服务。一般设置为true,设为常驻服务。false表示按需启动,访问到该SA时才会被拉起。该注册仅仅为向sa_main进程注册,即需要向samgr注册请求的SA,调用Publish才真正完成samgr的注册。
-
实现基类SystemAbility的OnStart,此函数由sa_main进程统一调用,需要调用SystemAbility基类提供的Publish方法实现SA向samgr注册
// base\telephony\core_service\services\core\src\core_service.cppvoid CoreService::OnStart(){... if (!registerToService_) { bool ret = Publish(DelayedSingleton::GetInstance().get()); if (!ret) { TELEPHONY_LOGE("CoreService::Init Publish failed!"); return; } registerToService_ = true; }...}
至此,SA代码部份介绍完成。
1.2.5 基它
ServiceId配置策略
-
每个子系统可配置的Id为100个,理论上足够了。
-
新加子系统SA配置,必须以如下开头和结尾。
SUBSYS_子系统名_SYS_ABILITY_ID_BEGINSUBSYS_子系统名_SYS_ABILITY_ID_END
-
新加的ServiceId不能出现重复,否则会出现Service分布式访问不通情况。