ServiceExtensionAbility
概述
[ServiceExtensionAbility]是SERVICE類型的ExtensionAbility組件,提供后臺(tái)服務(wù)能力,其內(nèi)部持有了一個(gè)[ServiceExtensionContext],通過[ServiceExtensionContext]提供了豐富的接口供外部使用。
本文描述中稱被啟動(dòng)的ServiceExtensionAbility為服務(wù)端,稱啟動(dòng)ServiceExtensionAbility的組件為客戶端。
[ServiceExtensionAbility]可以被其他組件啟動(dòng)或連接,并根據(jù)調(diào)用者的請(qǐng)求信息在后臺(tái)處理相關(guān)事務(wù)。[ServiceExtensionAbility]支持以啟動(dòng)和連接兩種形式運(yùn)行,系統(tǒng)應(yīng)用可以調(diào)用[startServiceExtensionAbility()]方法啟動(dòng)后臺(tái)服務(wù),也可以調(diào)用[connectServiceExtensionAbility()]方法連接后臺(tái)服務(wù),而三方應(yīng)用只能調(diào)用[connectServiceExtensionAbility()]方法連接后臺(tái)服務(wù)。啟動(dòng)和連接后臺(tái)服務(wù)的差別:
- 啟動(dòng) :AbilityA啟動(dòng)ServiceB,啟動(dòng)后AbilityA和ServiceB為弱關(guān)聯(lián),AbilityA退出后,ServiceB可以繼續(xù)存在。
- 連接 :AbilityA連接ServiceB,連接后AbilityA和ServiceB為強(qiáng)關(guān)聯(lián),AbilityA退出后,ServiceB也一起退出。
此處有如下細(xì)節(jié)需要注意:
- 若Service只通過connect的方式被拉起,那么該Service的生命周期將受客戶端控制,當(dāng)客戶端調(diào)用一次[connectServiceExtensionAbility()]方法,將建立一個(gè)連接,當(dāng)客戶端退出或者調(diào)用[disconnectServiceExtensionAbility()]方法,該連接將斷開。當(dāng)所有連接都斷開后,Service將自動(dòng)退出。
- Service一旦通過start的方式被拉起,將不會(huì)自動(dòng)退出,系統(tǒng)應(yīng)用可以調(diào)用[stopServiceExtensionAbility()]方法將Service退出。
- 只能在主線程線程中執(zhí)行connect/disconnect操作,不要在Worker、TaskPool等子線程中執(zhí)行connect/disconnect操作。
說明:
開發(fā)前請(qǐng)熟悉鴻蒙開發(fā)指導(dǎo)文檔 :gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md
點(diǎn)擊或者復(fù)制轉(zhuǎn)到。
- 當(dāng)前不支持三方應(yīng)用實(shí)現(xiàn)ServiceExtensionAbility。如果三方開發(fā)者想要實(shí)現(xiàn)后臺(tái)處理相關(guān)事務(wù)的功能,可以使用后臺(tái)任務(wù),具體請(qǐng)參見[后臺(tái)任務(wù)]。
- 三方應(yīng)用的UIAbility組件可以通過Context連接系統(tǒng)提供的ServiceExtensionAbility。
- 三方應(yīng)用需要在前臺(tái)獲焦的情況下才能連接系統(tǒng)提供的ServiceExtensionAbility。
生命周期
[ServiceExtensionAbility]提供了onCreate()、onRequest()、onConnect()、onDisconnect()和onDestroy()生命周期回調(diào),根據(jù)需要重寫對(duì)應(yīng)的回調(diào)方法。下圖展示了ServiceExtensionAbility的生命周期。
圖1 ServiceExtensionAbility生命周期
- onCreate 服務(wù)被首次創(chuàng)建時(shí)觸發(fā)該回調(diào),開發(fā)者可以在此進(jìn)行一些初始化的操作,例如注冊(cè)公共事件監(jiān)聽等。
說明: 如果服務(wù)已創(chuàng)建,再次啟動(dòng)該ServiceExtensionAbility不會(huì)觸發(fā)onCreate()回調(diào)。
- onRequest 當(dāng)另一個(gè)組件調(diào)用[startServiceExtensionAbility()]方法啟動(dòng)該服務(wù)組件時(shí),觸發(fā)該回調(diào)。執(zhí)行此方法后,服務(wù)會(huì)啟動(dòng)并在后臺(tái)運(yùn)行。每調(diào)用一次[startServiceExtensionAbility()]方法均會(huì)觸發(fā)該回調(diào)。
- onConnect 當(dāng)另一個(gè)組件調(diào)用[connectServiceExtensionAbility()]方法與該服務(wù)連接時(shí),觸發(fā)該回調(diào)。開發(fā)者在此方法中,返回一個(gè)遠(yuǎn)端代理對(duì)象(IRemoteObject),客戶端拿到這個(gè)對(duì)象后可以通過這個(gè)對(duì)象與服務(wù)端進(jìn)行RPC通信,同時(shí)系統(tǒng)側(cè)也會(huì)將該遠(yuǎn)端代理對(duì)象(IRemoteObject)儲(chǔ)存。后續(xù)若有組件再調(diào)用[connectServiceExtensionAbility()]方法,系統(tǒng)側(cè)會(huì)直接將所保存的遠(yuǎn)端代理對(duì)象(IRemoteObject)返回,而不再觸發(fā)該回調(diào)。
- onDisconnect 當(dāng)最后一個(gè)連接斷開時(shí),將觸發(fā)該回調(diào)??蛻舳怂劳龌蛘哒{(diào)用[disconnectServiceExtensionAbility()]方法可以使連接斷開。
- onDestroy 當(dāng)不再使用服務(wù)且準(zhǔn)備將其銷毀該實(shí)例時(shí),觸發(fā)該回調(diào)。開發(fā)者可以在該回調(diào)中清理資源,如注銷監(jiān)聽等。
實(shí)現(xiàn)一個(gè)后臺(tái)服務(wù)(僅對(duì)系統(tǒng)應(yīng)用開放)
開發(fā)準(zhǔn)備
只有系統(tǒng)應(yīng)用才允許實(shí)現(xiàn)ServiceExtensionAbility,因此開發(fā)者在開發(fā)之前需做如下準(zhǔn)備:
- 替換Full SDK :ServiceExtensionAbility相關(guān)接口都被標(biāo)記為System-API,默認(rèn)對(duì)開發(fā)者隱藏,因此需要手動(dòng)從鏡像站點(diǎn)獲取Full SDK,并在DevEco Studio中替換,具體操作可參考[替換指南]。
- 申請(qǐng)AllowAppUsePrivilegeExtension特權(quán) :只有具有AllowAppUsePrivilegeExtension特權(quán)的應(yīng)用才允許開發(fā)ServiceExtensionAbility,具體申請(qǐng)方式可參考[應(yīng)用特權(quán)配置指南])。
定義IDL接口
ServiceExtensionAbility作為后臺(tái)服務(wù),需要向外部提供可調(diào)用的接口,開發(fā)者可將接口定義在idl文件中,并使用[IDL工具]生成對(duì)應(yīng)的proxy、stub文件。此處定義一個(gè)名為IIdlServiceExt.idl的文件作為示例:
interface OHOS.IIdlServiceExt {
int ProcessData([in] int data);
void InsertDataToMap([in] String key, [in] int val);
}
在DevEco Studio工程Module對(duì)應(yīng)的ets目錄下手動(dòng)新建名為IdlServiceExt的目錄,將[IDL工具]生成的文件復(fù)制到該目錄下,并創(chuàng)建一個(gè)名為idl_service_ext_impl.ts的文件,作為idl接口的實(shí)現(xiàn):
├── ets
│ ├── IdlServiceExt
│ │ ├── i_idl_service_ext.ts # 生成文件
│ │ ├── idl_service_ext_proxy.ts # 生成文件
│ │ ├── idl_service_ext_stub.ts # 生成文件
│ │ ├── idl_service_ext_impl.ts # 開發(fā)者自定義文件,對(duì)idl接口的具體實(shí)現(xiàn)
│ └
└
idl_service_ext_impl.ts實(shí)現(xiàn)如下:
import IdlServiceExtStub from './idl_service_ext_stub';
import hilog from '@ohos.hilog';
import type { insertDataToMapCallback } from './i_idl_service_ext';
import type { processDataCallback } from './i_idl_service_ext';
const ERR_OK = 0;
const TAG: string = "[IdlServiceExtImpl]";
const DOMAIN_NUMBER: number = 0xFF00;
// 開發(fā)者需要在這個(gè)類型里對(duì)接口進(jìn)行實(shí)現(xiàn)
export default class ServiceExtImpl extends IdlServiceExtStub {
processData(data: number, callback: processDataCallback): void {
// 開發(fā)者自行實(shí)現(xiàn)業(yè)務(wù)邏輯
hilog.info(DOMAIN_NUMBER, TAG, `processData: ${data}`);
callback(ERR_OK, data + 1); // 鑒權(quán)通過,執(zhí)行正常業(yè)務(wù)邏輯
}
insertDataToMap(key: string, val: number, callback: insertDataToMapCallback): void {
// 開發(fā)者自行實(shí)現(xiàn)業(yè)務(wù)邏輯
hilog.info(DOMAIN_NUMBER, TAG, `insertDataToMap, key: ${key} val: ${val}`);
callback(ERR_OK);
}
}
創(chuàng)建ServiceExtensionAbility
在DevEco Studio工程中手動(dòng)新建一個(gè)ServiceExtensionAbility,具體步驟如下:
- 在工程Module對(duì)應(yīng)的ets目錄下,右鍵選擇“New > Directory”,新建一個(gè)目錄并命名為ServiceExtAbility。
- 在ServiceExtAbility目錄,右鍵選擇“New > ArkTS File”,新建一個(gè)文件并命名為ServiceExtAbility.ets。
├── ets │ ├── IdlServiceExt │ │ ├── i_idl_service_ext.ets # 生成文件 │ │ ├── idl_service_ext_proxy.ets # 生成文件 │ │ ├── idl_service_ext_stub.ets # 生成文件 │ │ ├── idl_service_ext_impl.ets # 開發(fā)者自定義文件,對(duì)idl接口的具體實(shí)現(xiàn) │ ├── ServiceExtAbility │ │ ├── ServiceExtAbility.ets └
- 在ServiceExtAbility.ets文件中,增加導(dǎo)入ServiceExtensionAbility的依賴包,自定義類繼承ServiceExtensionAbility并實(shí)現(xiàn)生命周期回調(diào),在onConnect生命周期回調(diào)里,需要將之前定義的ServiceExtImpl對(duì)象返回。
import hilog from '@ohos.hilog'; import ServiceExtensionAbility from '@ohos.app.ability.ServiceExtensionAbility'; import ServiceExtImpl from '../IdlServiceExt/idl_service_ext_impl'; import type Want from '@ohos.app.ability.Want'; import type rpc from '@ohos.rpc'; const TAG: string = '[ServiceExtAbility]'; const DOMAIN_NUMBER: number = 0xFF00; export default class ServiceExtAbility extends ServiceExtensionAbility { serviceExtImpl: ServiceExtImpl = new ServiceExtImpl('ExtImpl'); onCreate(want: Want): void { let serviceExtensionContext = this.context; hilog.info(DOMAIN_NUMBER, TAG, `onCreate, want: ${want.abilityName}`); }; onRequest(want: Want, startId: number): void { hilog.info(DOMAIN_NUMBER, TAG, `onRequest, want: ${want.abilityName}`); }; onConnect(want: Want): rpc.RemoteObject { hilog.info(DOMAIN_NUMBER, TAG, `onConnect, want: ${want.abilityName}`); // 返回ServiceExtImpl對(duì)象,客戶端獲取后便可以與ServiceExtensionAbility進(jìn)行通信 return this.serviceExtImpl as rpc.RemoteObject; }; onDisconnect(want: Want): void { hilog.info(DOMAIN_NUMBER, TAG, `onDisconnect, want: ${want.abilityName}`); }; onDestroy(): void { hilog.info(DOMAIN_NUMBER, TAG, 'onDestroy'); }; };
- 在工程Module對(duì)應(yīng)的[module.json5配置文件]中注冊(cè)ServiceExtensionAbility,type標(biāo)簽需要設(shè)置為“service”,srcEntry標(biāo)簽表示當(dāng)前ExtensionAbility組件所對(duì)應(yīng)的代碼路徑。
{ "module": { ... "extensionAbilities": [ { "name": "ServiceExtAbility", "icon": "$media:icon", "description": "service", "type": "service", "exported": true, "srcEntry": "./ets/ServiceExtAbility/ServiceExtAbility.ets" } ] } }
啟動(dòng)一個(gè)后臺(tái)服務(wù)(僅對(duì)系統(tǒng)應(yīng)用開放)
系統(tǒng)應(yīng)用通過[startServiceExtensionAbility()]方法啟動(dòng)一個(gè)后臺(tái)服務(wù),服務(wù)的[onRequest()]回調(diào)就會(huì)被調(diào)用,并在該回調(diào)方法中接收到調(diào)用者傳遞過來的want對(duì)象。后臺(tái)服務(wù)啟動(dòng)后,其生命周期獨(dú)立于客戶端,即使客戶端已經(jīng)銷毀,該后臺(tái)服務(wù)仍可繼續(xù)運(yùn)行。因此,后臺(tái)服務(wù)需要在其工作完成時(shí)通過調(diào)用ServiceExtensionContext的[terminateSelf()]來自行停止,或者由另一個(gè)組件調(diào)用[stopServiceExtensionAbility()]來將其停止。
說明: ServiceExtensionContext的[startServiceExtensionAbility()]、[stopServiceExtensionAbility()]和[terminateSelf()]為系統(tǒng)接口,三方應(yīng)用不支持調(diào)用。
- 在系統(tǒng)應(yīng)用中啟動(dòng)一個(gè)新的ServiceExtensionAbility。示例中的context的獲取方式請(qǐng)參見[獲取UIAbility的上下文信息]。
import common from '@ohos.app.ability.common'; import Want from '@ohos.app.ability.Want'; import { BusinessError } from '@ohos.base'; import promptAction from '@ohos.promptAction'; import hilog from '@ohos.hilog'; const TAG: string = '[Page_ServiceExtensionAbility]'; const DOMAIN_NUMBER: number = 0xFF00; @Entry @Component struct Page_ServiceExtensionAbility { build() { Column() { //... List({ initialIndex: 0 }) { ListItem() { Row() { //... } .onClick(() = > { let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; // UIAbilityContext let want: Want = { deviceId: '', bundleName: 'com.samples.stagemodelabilitydevelop', abilityName: 'ServiceExtAbility' }; context.startServiceExtensionAbility(want).then(() = > { hilog.info(DOMAIN_NUMBER, TAG, 'Succeeded in starting ServiceExtensionAbility.'); // 成功啟動(dòng)后臺(tái)服務(wù) promptAction.showToast({ message: $r('app.string.SuccessfullyStartBackendService') }); }).catch((err: BusinessError) = > { hilog.error(DOMAIN_NUMBER, TAG, `Failed to start ServiceExtensionAbility. Code is ${err.code}, message is ${err.message}`); }); }) } //... } //... } //... } }
- 在系統(tǒng)應(yīng)用中停止一個(gè)已啟動(dòng)的ServiceExtensionAbility。
import common from '@ohos.app.ability.common'; import hilog from '@ohos.hilog'; import promptAction from '@ohos.promptAction'; import Want from '@ohos.app.ability.Want'; import { BusinessError } from '@ohos.base'; const TAG: string = '[Page_ServiceExtensionAbility]'; const DOMAIN_NUMBER: number = 0xFF00; @Entry @Component struct Page_ServiceExtensionAbility { build() { Column() { //... List({ initialIndex: 0 }) { ListItem() { Row() { //... } .onClick(() = > { let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; // UIAbilityContext let want: Want = { deviceId: '', bundleName: 'com.samples.stagemodelabilitydevelop', abilityName: 'ServiceExtAbility' }; context.stopServiceExtensionAbility(want).then(() = > { hilog.info(DOMAIN_NUMBER, TAG, 'Succeeded in stopping ServiceExtensionAbility.'); promptAction.showToast({ message: $r('app.string.SuccessfullyStoppedAStartedBackendService') }); }).catch((err: BusinessError) = > { hilog.error(DOMAIN_NUMBER, TAG, `Failed to stop ServiceExtensionAbility. Code is ${err.code}, message is ${err.message}`); }); }) } //... } //... } //... } }
- 已啟動(dòng)的ServiceExtensionAbility停止自身。
import common from '@ohos.app.ability.common'; import { BusinessError } from '@ohos.base'; import promptAction from '@ohos.promptAction'; import hilog from '@ohos.hilog'; import Want from '@ohos.app.ability.Want'; const TAG: string = '[Page_ServiceExtensionAbility]'; const DOMAIN_NUMBER: number = 0xFF00; @Entry @Component struct Page_ServiceExtensionAbility { build() { Column() { //... List({ initialIndex: 0 }) { ListItem() { Row() { //... } .onClick(() = > { let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; // UIAbilityContext context.terminateSelf().then(() = > { hilog.info(DOMAIN_NUMBER, TAG, 'Succeeded in terminating self.'); // 成功停止當(dāng)前后臺(tái)服務(wù) promptAction.showToast({ message: $r('app.string.SuccessfullyStopStartedBackendService') }); }).catch((err: BusinessError) = > { hilog.error(DOMAIN_NUMBER, TAG, `Failed to terminate self. Code is ${err.code}, message is ${err.message}`); }); }) } //... } //... } //... } }
說明: 后臺(tái)服務(wù)可以在后臺(tái)長(zhǎng)期運(yùn)行,為了避免資源浪費(fèi),需要對(duì)后臺(tái)服務(wù)的生命周期進(jìn)行管理。即一個(gè)后臺(tái)服務(wù)完成了請(qǐng)求方的任務(wù),需要及時(shí)銷毀。銷毀已啟動(dòng)的后臺(tái)服務(wù)有兩種方式:
- 后臺(tái)服務(wù)自身調(diào)用[terminateSelf()]方法來自行停止。
- 由其他組件調(diào)用[stopServiceExtensionAbility()]方法來停止。 調(diào)用[terminateSelf()]或[stopServiceExtensionAbility()]方法之后,系統(tǒng)將銷毀后臺(tái)服務(wù)。
連接一個(gè)后臺(tái)服務(wù)
系統(tǒng)應(yīng)用或者三方應(yīng)用可以通過[connectServiceExtensionAbility()]連接一個(gè)服務(wù)(在Want對(duì)象中指定啟動(dòng)的目標(biāo)服務(wù)),服務(wù)的[onConnect()]就會(huì)被調(diào)用,并在該回調(diào)方法中接收到調(diào)用者傳遞過來的Want對(duì)象,從而建立長(zhǎng)連接。
ServiceExtensionAbility服務(wù)組件在[onConnect()]中返回IRemoteObject對(duì)象,開發(fā)者通過該IRemoteObject定義通信接口,用于客戶端與服務(wù)端進(jìn)行RPC交互。多個(gè)客戶端可以同時(shí)連接到同一個(gè)后臺(tái)服務(wù),客戶端完成與服務(wù)的交互后,客戶端需要通過調(diào)用[disconnectServiceExtensionAbility()]來斷開連接。如果所有連接到某個(gè)后臺(tái)服務(wù)的客戶端均已斷開連接,則系統(tǒng)會(huì)銷毀該服務(wù)。
- 使用connectServiceExtensionAbility()建立與后臺(tái)服務(wù)的連接。示例中的context的獲取方式請(qǐng)參見[獲取UIAbility的上下文信息]。
import common from '@ohos.app.ability.common'; import deviceManager from '@ohos.distributedDeviceManager'; import hilog from '@ohos.hilog'; import promptAction from '@ohos.promptAction'; import rpc from '@ohos.rpc'; import Want from '@ohos.app.ability.Want'; // 客戶端需要將服務(wù)端對(duì)外提供的idl_service_ext_proxy.ts導(dǎo)入到本地工程中 import IdlServiceExtProxy from '../IdlServiceExt/idl_service_ext_proxy'; const TAG: string = '[Page_ServiceExtensionAbility]'; const DOMAIN_NUMBER: number = 0xFF00; let connectionId: number; let want: Want = { deviceId: '', bundleName: 'com.samples.stagemodelabilitydevelop', abilityName: 'ServiceExtAbility' }; let options: common.ConnectOptions = { onConnect(elementName, remote: rpc.IRemoteObject): void { hilog.info(DOMAIN_NUMBER, TAG, 'onConnect callback'); if (remote === null) { hilog.info(DOMAIN_NUMBER, TAG, `onConnect remote is null`); return; } let serviceExtProxy: IdlServiceExtProxy = new IdlServiceExtProxy(remote); // 通過接口調(diào)用的方式進(jìn)行通信,屏蔽了RPC通信的細(xì)節(jié),簡(jiǎn)潔明了 serviceExtProxy.processData(1, (errorCode: number, retVal: number) = > { hilog.info(DOMAIN_NUMBER, TAG, `processData, errorCode: ${errorCode}, retVal: ${retVal}`); }); serviceExtProxy.insertDataToMap('theKey', 1, (errorCode: number) = > { hilog.info(DOMAIN_NUMBER, TAG, `insertDataToMap, errorCode: ${errorCode}`); }) }, onDisconnect(elementName): void { hilog.info(DOMAIN_NUMBER, TAG, 'onDisconnect callback'); }, onFailed(code: number): void { hilog.info(DOMAIN_NUMBER, TAG, 'onFailed callback', JSON.stringify(code)); } }; @Entry @Component struct Page_ServiceExtensionAbility { build() { Column() { //... List({ initialIndex: 0 }) { ListItem() { Row() { //... } .onClick(() = > { let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; // UIAbilityContext // 建立連接后返回的Id需要保存下來,在解綁服務(wù)時(shí)需要作為參數(shù)傳入 connectionId = context.connectServiceExtensionAbility(want, options); // 成功連接后臺(tái)服務(wù) promptAction.showToast({ message: $r('app.string.SuccessfullyConnectBackendService') }); // connectionId = context.connectAbility(want, options); hilog.info(DOMAIN_NUMBER, TAG, `connectionId is : ${connectionId}`); }) } //... } //... } //... } }
- 使用disconnectServiceExtensionAbility()斷開與后臺(tái)服務(wù)的連接。
import hilog from '@ohos.hilog'; import promptAction from '@ohos.promptAction'; import common from '@ohos.app.ability.common'; import { BusinessError } from '@ohos.base'; const TAG: string = '[Page_ServiceExtensionAbility]'; const DOMAIN_NUMBER: number = 0xFF00; let connectionId: number; @Entry @Component struct Page_ServiceExtensionAbility { build() { Column() { //... List({ initialIndex: 0 }) { ListItem() { Row() { //... } .onClick(() = > { let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; // UIAbilityContext // connectionId為調(diào)用connectServiceExtensionAbility接口時(shí)的返回值,需開發(fā)者自行維護(hù) context.disconnectServiceExtensionAbility(connectionId).then(() = > { hilog.info(DOMAIN_NUMBER, TAG, 'disconnectServiceExtensionAbility success'); // 成功斷連后臺(tái)服務(wù) promptAction.showToast({ message: $r('app.string.SuccessfullyDisconnectBackendService') }); }).catch((error: BusinessError) = > { hilog.error(DOMAIN_NUMBER, TAG, 'disconnectServiceExtensionAbility failed'); }); }) } //... } //... } //... } }
客戶端與服務(wù)端通信
客戶端在onConnect()中獲取到[rpc.RemoteObject]對(duì)象后便可與Service進(jìn)行通信,有如下兩種方式:
- 使用服務(wù)端提供的IDL接口進(jìn)行通信(推薦)
// 客戶端需要將服務(wù)端對(duì)外提供的idl_service_ext_proxy.ts導(dǎo)入到本地工程中 import common from '@ohos.app.ability.common'; import hilog from '@ohos.hilog'; import rpc from '@ohos.rpc'; import IdlServiceExtProxy from '../IdlServiceExt/idl_service_ext_proxy'; const TAG: string = '[Page_ServiceExtensionAbility]'; const DOMAIN_NUMBER: number = 0xFF00; let options: common.ConnectOptions = { onConnect(elementName, remote: rpc.IRemoteObject): void { hilog.info(DOMAIN_NUMBER, TAG, 'onConnect callback'); if (remote === null) { hilog.info(DOMAIN_NUMBER, TAG, `onConnect remote is null`); return; } let serviceExtProxy: IdlServiceExtProxy = new IdlServiceExtProxy(remote); // 通過接口調(diào)用的方式進(jìn)行通信,屏蔽了RPC通信的細(xì)節(jié),簡(jiǎn)潔明了 serviceExtProxy.processData(1, (errorCode: number, retVal: number) = > { hilog.info(DOMAIN_NUMBER, TAG, `processData, errorCode: ${errorCode}, retVal: ${retVal}`); }); serviceExtProxy.insertDataToMap('theKey', 1, (errorCode: number) = > { hilog.info(DOMAIN_NUMBER, TAG, `insertDataToMap, errorCode: ${errorCode}`); }) }, onDisconnect(elementName): void { hilog.info(DOMAIN_NUMBER, TAG, 'onDisconnect callback'); }, onFailed(code: number): void { hilog.info(DOMAIN_NUMBER, TAG, 'onFailed callback', JSON.stringify(code)); } };
- 直接使用[sendMessageRequest]接口向服務(wù)端發(fā)送消息(不推薦)
import hilog from '@ohos.hilog'; import promptAction from '@ohos.promptAction'; import rpc from '@ohos.rpc'; import common from '@ohos.app.ability.common'; import { BusinessError } from '@ohos.base'; const TAG: string = '[Page_CollaborateAbility]'; const DOMAIN_NUMBER: number = 0xFF00; const REQUEST_CODE = 1; let options: common.ConnectOptions = { onConnect(elementName, remote): void { hilog.info(DOMAIN_NUMBER, TAG, 'onConnect callback'); if (remote === null) { hilog.info(DOMAIN_NUMBER, TAG, `onConnect remote is null`); return; } let option = new rpc.MessageOption(); let data = new rpc.MessageSequence(); let reply = new rpc.MessageSequence(); data.writeInt(99); // 開發(fā)者可發(fā)送data到目標(biāo)端應(yīng)用進(jìn)行相應(yīng)操作 // @param code 表示客戶端發(fā)送的服務(wù)請(qǐng)求代碼。 // @param data 表示客戶端發(fā)送的{@link MessageSequence}對(duì)象。 // @param reply 表示遠(yuǎn)程服務(wù)發(fā)送的響應(yīng)消息對(duì)象。 // @param options 指示操作是同步的還是異步的。 // @return 如果操作成功返回{@code true}; 否則返回 {@code false}。 remote.sendMessageRequest(REQUEST_CODE, data, reply, option).then((ret: rpc.RequestResult) = > { let errCode = reply.readInt(); // 在成功連接的情況下,會(huì)收到來自目標(biāo)端返回的信息(100) let msg: number = 0; if (errCode === 0) { msg = reply.readInt(); } hilog.info(DOMAIN_NUMBER, TAG, `sendRequest msg:${msg}`); // 成功連接后臺(tái)服務(wù) promptAction.showToast({ message: `sendRequest msg:${msg}` }); }).catch((error: BusinessError) = > { hilog.info(DOMAIN_NUMBER, TAG, `sendRequest failed, ${JSON.stringify(error)}`); }); }, onDisconnect(elementName): void { hilog.info(DOMAIN_NUMBER, TAG, 'onDisconnect callback'); }, onFailed(code): void { hilog.info(DOMAIN_NUMBER, TAG, 'onFailed callback'); } }; //...
服務(wù)端對(duì)客戶端身份校驗(yàn)
部分開發(fā)者需要使用ServiceExtension提供一些較為敏感的服務(wù),因此需要對(duì)客戶端身份進(jìn)行校驗(yàn),開發(fā)者可在IDL接口的stub端進(jìn)行校驗(yàn),IDL接口實(shí)現(xiàn)詳見上文[定義IDL接口],此處推薦兩種校驗(yàn)方式:
- 通過callerUid識(shí)別客戶端應(yīng)用
通過調(diào)用[getCallingUid()]接口獲取客戶端的uid,再調(diào)用[getBundleNameByUid()]接口獲取uid對(duì)應(yīng)的bundleName,從而識(shí)別客戶端身份。此處需要注意的是[getBundleNameByUid()]是一個(gè)異步接口,因此服務(wù)端無法將校驗(yàn)結(jié)果返回給客戶端,這種校驗(yàn)方式適合客戶端向服務(wù)端發(fā)起執(zhí)行異步任務(wù)請(qǐng)求的場(chǎng)景,示例代碼如下:import abilityAccessCtrl from '@ohos.abilityAccessCtrl'; import bundleManager from '@ohos.bundle.bundleManager'; import IdlServiceExtStub from './idl_service_ext_stub'; import hilog from '@ohos.hilog'; import rpc from '@ohos.rpc'; import type { BusinessError } from '@ohos.base'; import type { InsertDataToMapCallback } from './i_idl_service_ext'; import type { ProcessDataCallback } from './i_idl_service_ext'; const ERR_OK = 0; const ERR_DENY = -1; const TAG: string = "[IdlServiceExtImpl]"; const DOMAIN_NUMBER: number = 0xFF00; // 開發(fā)者需要在這個(gè)類型里對(duì)接口進(jìn)行實(shí)現(xiàn) export default class ServiceExtImpl extends IdlServiceExtStub { processData(data: number, callback: ProcessDataCallback): void { // 開發(fā)者自行實(shí)現(xiàn)業(yè)務(wù)邏輯 hilog.info(DOMAIN_NUMBER, TAG, `processData: ${data}`); let callerUid = rpc.IPCSkeleton.getCallingUid(); bundleManager.getBundleNameByUid(callerUid).then((callerBundleName) = > { hilog.info(DOMAIN_NUMBER, TAG, 'getBundleNameByUid: ' + callerBundleName); // 對(duì)客戶端包名進(jìn)行識(shí)別 if (callerBundleName !== 'com.samples.stagemodelabilitydevelop') { // 識(shí)別不通過 hilog.info(DOMAIN_NUMBER, TAG, 'The caller bundle is not in trustlist, reject'); return; } // 識(shí)別通過,執(zhí)行正常業(yè)務(wù)邏輯 }).catch((err: BusinessError) = > { hilog.info(DOMAIN_NUMBER, TAG, 'getBundleNameByUid failed: ' + err.message); }); //... }; insertDataToMap(key: string, val: number, callback: InsertDataToMapCallback): void { // 開發(fā)者自行實(shí)現(xiàn)業(yè)務(wù)邏輯 hilog.info(DOMAIN_NUMBER, TAG, `insertDataToMap, key: ${key} val: ${val}`); callback(ERR_OK); }; };
- 通過callerTokenId對(duì)客戶端進(jìn)行鑒權(quán)
通過調(diào)用[getCallingTokenId()]接口獲取客戶端的tokenID,再調(diào)用[verifyAccessTokenSync()]接口判斷客戶端是否有某個(gè)具體權(quán)限,由于當(dāng)前不支持自定義權(quán)限,因此只能校驗(yàn)當(dāng)前[系統(tǒng)所定義的權(quán)限]。示例代碼如下:import abilityAccessCtrl from '@ohos.abilityAccessCtrl'; import bundleManager from '@ohos.bundle.bundleManager'; import IdlServiceExtStub from './idl_service_ext_stub'; import hilog from '@ohos.hilog'; import rpc from '@ohos.rpc'; import type { BusinessError } from '@ohos.base'; import type { InsertDataToMapCallback } from './i_idl_service_ext'; import type { ProcessDataCallback } from './i_idl_service_ext'; const ERR_OK = 0; const ERR_DENY = -1; const TAG: string = '[IdlServiceExtImpl]'; const DOMAIN_NUMBER: number = 0xFF00; // 開發(fā)者需要在這個(gè)類型里對(duì)接口進(jìn)行實(shí)現(xiàn) export default class ServiceExtImpl extends IdlServiceExtStub { processData(data: number, callback: ProcessDataCallback): void { // 開發(fā)者自行實(shí)現(xiàn)業(yè)務(wù)邏輯 hilog.info(DOMAIN_NUMBER, TAG, `processData: ${data}`); let callerUid = rpc.IPCSkeleton.getCallingUid(); bundleManager.getBundleNameByUid(callerUid).then((callerBundleName) = > { hilog.info(DOMAIN_NUMBER, TAG, 'getBundleNameByUid: ' + callerBundleName); // 對(duì)客戶端包名進(jìn)行識(shí)別 if (callerBundleName !== 'com.samples.stagemodelabilitydevelop') { // 識(shí)別不通過 hilog.info(DOMAIN_NUMBER, TAG, 'The caller bundle is not in trustlist, reject'); return; } // 識(shí)別通過,執(zhí)行正常業(yè)務(wù)邏輯 }).catch((err: BusinessError) = > { hilog.info(DOMAIN_NUMBER, TAG, 'getBundleNameByUid failed: ' + err.message); }); let callerTokenId = rpc.IPCSkeleton.getCallingTokenId(); let accessManger = abilityAccessCtrl.createAtManager(); // 所校驗(yàn)的具體權(quán)限由開發(fā)者自行選擇,此處ohos.permission.GET_BUNDLE_INFO_PRIVILEGED只作為示例 let grantStatus = accessManger.verifyAccessTokenSync(callerTokenId, 'ohos.permission.GET_BUNDLE_INFO_PRIVILEGED'); if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_DENIED) { hilog.info(DOMAIN_NUMBER, TAG, 'PERMISSION_DENIED'); callback(ERR_DENY, data); // 鑒權(quán)失敗,返回錯(cuò)誤 return; } hilog.info(DOMAIN_NUMBER, TAG, 'verify access token success.'); callback(ERR_OK, data + 1); // 鑒權(quán)通過,執(zhí)行正常業(yè)務(wù)邏輯 }; insertDataToMap(key: string, val: number, callback: InsertDataToMapCallback): void { // 開發(fā)者自行實(shí)現(xiàn)業(yè)務(wù)邏輯 hilog.info(DOMAIN_NUMBER, TAG, `insertDataToMap, key: ${key} val: ${val}`); callback(ERR_OK); }; };
審核編輯 黃宇
-
框架
+關(guān)注
關(guān)注
0文章
398瀏覽量
17405 -
程序
+關(guān)注
關(guān)注
116文章
3762瀏覽量
80754 -
鴻蒙
+關(guān)注
關(guān)注
57文章
2302瀏覽量
42689
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論