电子说
[ServiceExtensionAbility]是SERVICE类型的ExtensionAbility组件,提供后台服务能力,其内部持有了一个[ServiceExtensionContext],通过[ServiceExtensionContext]提供了丰富的接口供外部使用。
本文描述中称被启动的ServiceExtensionAbility为服务端,称启动ServiceExtensionAbility的组件为客户端。
[ServiceExtensionAbility]可以被其他组件启动或连接,并根据调用者的请求信息在后台处理相关事务。[ServiceExtensionAbility]支持以启动和连接两种形式运行,系统应用可以调用[startServiceExtensionAbility()]方法启动后台服务,也可以调用[connectServiceExtensionAbility()]方法连接后台服务,而三方应用只能调用[connectServiceExtensionAbility()]方法连接后台服务。启动和连接后台服务的差别:
此处有如下细节需要注意:
说明:
开发前请熟悉鸿蒙开发指导文档 :gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md
点击或者复制转到。
- 当前不支持三方应用实现ServiceExtensionAbility。如果三方开发者想要实现后台处理相关事务的功能,可以使用后台任务,具体请参见[后台任务]。
- 三方应用的UIAbility组件可以通过Context连接系统提供的ServiceExtensionAbility。
- 三方应用需要在前台获焦的情况下才能连接系统提供的ServiceExtensionAbility。
[ServiceExtensionAbility]提供了onCreate()、onRequest()、onConnect()、onDisconnect()和onDestroy()生命周期回调,根据需要重写对应的回调方法。下图展示了ServiceExtensionAbility的生命周期。
图1 ServiceExtensionAbility生命周期
说明: 如果服务已创建,再次启动该ServiceExtensionAbility不会触发onCreate()回调。
只有系统应用才允许实现ServiceExtensionAbility,因此开发者在开发之前需做如下准备:
ServiceExtensionAbility作为后台服务,需要向外部提供可调用的接口,开发者可将接口定义在idl文件中,并使用[IDL工具]生成对应的proxy、stub文件。此处定义一个名为IIdlServiceExt.idl的文件作为示例:
interface OHOS.IIdlServiceExt {
int ProcessData([in] int data);
void InsertDataToMap([in] String key, [in] int val);
}
在DevEco Studio工程Module对应的ets目录下手动新建名为IdlServiceExt的目录,将[IDL工具]生成的文件复制到该目录下,并创建一个名为idl_service_ext_impl.ts的文件,作为idl接口的实现:
├── ets
│ ├── IdlServiceExt
│ │ ├── i_idl_service_ext.ts # 生成文件
│ │ ├── idl_service_ext_proxy.ts # 生成文件
│ │ ├── idl_service_ext_stub.ts # 生成文件
│ │ ├── idl_service_ext_impl.ts # 开发者自定义文件,对idl接口的具体实现
│ └
└
idl_service_ext_impl.ts实现如下:
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;
// 开发者需要在这个类型里对接口进行实现
export default class ServiceExtImpl extends IdlServiceExtStub {
processData(data: number, callback: processDataCallback): void {
// 开发者自行实现业务逻辑
hilog.info(DOMAIN_NUMBER, TAG, `processData: ${data}`);
callback(ERR_OK, data + 1); // 鉴权通过,执行正常业务逻辑
}
insertDataToMap(key: string, val: number, callback: insertDataToMapCallback): void {
// 开发者自行实现业务逻辑
hilog.info(DOMAIN_NUMBER, TAG, `insertDataToMap, key: ${key} val: ${val}`);
callback(ERR_OK);
}
}
在DevEco Studio工程中手动新建一个ServiceExtensionAbility,具体步骤如下:
├── ets
│ ├── IdlServiceExt
│ │ ├── i_idl_service_ext.ets # 生成文件
│ │ ├── idl_service_ext_proxy.ets # 生成文件
│ │ ├── idl_service_ext_stub.ets # 生成文件
│ │ ├── idl_service_ext_impl.ets # 开发者自定义文件,对idl接口的具体实现
│ ├── ServiceExtAbility
│ │ ├── ServiceExtAbility.ets
└
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对象,客户端获取后便可以与ServiceExtensionAbility进行通信
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": {
...
"extensionAbilities": [
{
"name": "ServiceExtAbility",
"icon": "$media:icon",
"description": "service",
"type": "service",
"exported": true,
"srcEntry": "./ets/ServiceExtAbility/ServiceExtAbility.ets"
}
]
}
}
系统应用通过[startServiceExtensionAbility()]方法启动一个后台服务,服务的[onRequest()]回调就会被调用,并在该回调方法中接收到调用者传递过来的want对象。后台服务启动后,其生命周期独立于客户端,即使客户端已经销毁,该后台服务仍可继续运行。因此,后台服务需要在其工作完成时通过调用ServiceExtensionContext的[terminateSelf()]来自行停止,或者由另一个组件调用[stopServiceExtensionAbility()]来将其停止。
说明: ServiceExtensionContext的[startServiceExtensionAbility()]、[stopServiceExtensionAbility()]和[terminateSelf()]为系统接口,三方应用不支持调用。
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.');
// 成功启动后台服务
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}`);
});
})
}
//...
}
//...
}
//...
}
}
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}`);
});
})
}
//...
}
//...
}
//...
}
}
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.');
// 成功停止当前后台服务
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}`);
});
})
}
//...
}
//...
}
//...
}
}
说明: 后台服务可以在后台长期运行,为了避免资源浪费,需要对后台服务的生命周期进行管理。即一个后台服务完成了请求方的任务,需要及时销毁。销毁已启动的后台服务有两种方式:
- 后台服务自身调用[terminateSelf()]方法来自行停止。
- 由其他组件调用[stopServiceExtensionAbility()]方法来停止。 调用[terminateSelf()]或[stopServiceExtensionAbility()]方法之后,系统将销毁后台服务。
系统应用或者三方应用可以通过[connectServiceExtensionAbility()]连接一个服务(在Want对象中指定启动的目标服务),服务的[onConnect()]就会被调用,并在该回调方法中接收到调用者传递过来的Want对象,从而建立长连接。
ServiceExtensionAbility服务组件在[onConnect()]中返回IRemoteObject对象,开发者通过该IRemoteObject定义通信接口,用于客户端与服务端进行RPC交互。多个客户端可以同时连接到同一个后台服务,客户端完成与服务的交互后,客户端需要通过调用[disconnectServiceExtensionAbility()]来断开连接。如果所有连接到某个后台服务的客户端均已断开连接,则系统会销毁该服务。
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';
// 客户端需要将服务端对外提供的idl_service_ext_proxy.ts导入到本地工程中
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);
// 通过接口调用的方式进行通信,屏蔽了RPC通信的细节,简洁明了
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需要保存下来,在解绑服务时需要作为参数传入
connectionId = context.connectServiceExtensionAbility(want, options);
// 成功连接后台服务
promptAction.showToast({
message: $r('app.string.SuccessfullyConnectBackendService')
});
// connectionId = context.connectAbility(want, options);
hilog.info(DOMAIN_NUMBER, TAG, `connectionId is : ${connectionId}`);
})
}
//...
}
//...
}
//...
}
}
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为调用connectServiceExtensionAbility接口时的返回值,需开发者自行维护
context.disconnectServiceExtensionAbility(connectionId).then(() = > {
hilog.info(DOMAIN_NUMBER, TAG, 'disconnectServiceExtensionAbility success');
// 成功断连后台服务
promptAction.showToast({
message: $r('app.string.SuccessfullyDisconnectBackendService')
});
}).catch((error: BusinessError) = > {
hilog.error(DOMAIN_NUMBER, TAG, 'disconnectServiceExtensionAbility failed');
});
})
}
//...
}
//...
}
//...
}
}
客户端在onConnect()中获取到[rpc.RemoteObject]对象后便可与Service进行通信,有如下两种方式:
// 客户端需要将服务端对外提供的idl_service_ext_proxy.ts导入到本地工程中
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);
// 通过接口调用的方式进行通信,屏蔽了RPC通信的细节,简洁明了
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));
}
};
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);
// 开发者可发送data到目标端应用进行相应操作
// @param code 表示客户端发送的服务请求代码。
// @param data 表示客户端发送的{@link MessageSequence}对象。
// @param reply 表示远程服务发送的响应消息对象。
// @param options 指示操作是同步的还是异步的。
// @return 如果操作成功返回{@code true}; 否则返回 {@code false}。
remote.sendMessageRequest(REQUEST_CODE, data, reply, option).then((ret: rpc.RequestResult) = > {
let errCode = reply.readInt(); // 在成功连接的情况下,会收到来自目标端返回的信息(100)
let msg: number = 0;
if (errCode === 0) {
msg = reply.readInt();
}
hilog.info(DOMAIN_NUMBER, TAG, `sendRequest msg:${msg}`);
// 成功连接后台服务
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');
}
};
//...
部分开发者需要使用ServiceExtension提供一些较为敏感的服务,因此需要对客户端身份进行校验,开发者可在IDL接口的stub端进行校验,IDL接口实现详见上文[定义IDL接口],此处推荐两种校验方式:
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;
// 开发者需要在这个类型里对接口进行实现
export default class ServiceExtImpl extends IdlServiceExtStub {
processData(data: number, callback: ProcessDataCallback): void {
// 开发者自行实现业务逻辑
hilog.info(DOMAIN_NUMBER, TAG, `processData: ${data}`);
let callerUid = rpc.IPCSkeleton.getCallingUid();
bundleManager.getBundleNameByUid(callerUid).then((callerBundleName) = > {
hilog.info(DOMAIN_NUMBER, TAG, 'getBundleNameByUid: ' + callerBundleName);
// 对客户端包名进行识别
if (callerBundleName !== 'com.samples.stagemodelabilitydevelop') { // 识别不通过
hilog.info(DOMAIN_NUMBER, TAG, 'The caller bundle is not in trustlist, reject');
return;
}
// 识别通过,执行正常业务逻辑
}).catch((err: BusinessError) = > {
hilog.info(DOMAIN_NUMBER, TAG, 'getBundleNameByUid failed: ' + err.message);
});
//...
};
insertDataToMap(key: string, val: number, callback: InsertDataToMapCallback): void {
// 开发者自行实现业务逻辑
hilog.info(DOMAIN_NUMBER, TAG, `insertDataToMap, key: ${key} val: ${val}`);
callback(ERR_OK);
};
};
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;
// 开发者需要在这个类型里对接口进行实现
export default class ServiceExtImpl extends IdlServiceExtStub {
processData(data: number, callback: ProcessDataCallback): void {
// 开发者自行实现业务逻辑
hilog.info(DOMAIN_NUMBER, TAG, `processData: ${data}`);
let callerUid = rpc.IPCSkeleton.getCallingUid();
bundleManager.getBundleNameByUid(callerUid).then((callerBundleName) = > {
hilog.info(DOMAIN_NUMBER, TAG, 'getBundleNameByUid: ' + callerBundleName);
// 对客户端包名进行识别
if (callerBundleName !== 'com.samples.stagemodelabilitydevelop') { // 识别不通过
hilog.info(DOMAIN_NUMBER, TAG, 'The caller bundle is not in trustlist, reject');
return;
}
// 识别通过,执行正常业务逻辑
}).catch((err: BusinessError) = > {
hilog.info(DOMAIN_NUMBER, TAG, 'getBundleNameByUid failed: ' + err.message);
});
let callerTokenId = rpc.IPCSkeleton.getCallingTokenId();
let accessManger = abilityAccessCtrl.createAtManager();
// 所校验的具体权限由开发者自行选择,此处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); // 鉴权失败,返回错误
return;
}
hilog.info(DOMAIN_NUMBER, TAG, 'verify access token success.');
callback(ERR_OK, data + 1); // 鉴权通过,执行正常业务逻辑
};
insertDataToMap(key: string, val: number, callback: InsertDataToMapCallback): void {
// 开发者自行实现业务逻辑
hilog.info(DOMAIN_NUMBER, TAG, `insertDataToMap, key: ${key} val: ${val}`);
callback(ERR_OK);
};
};
审核编辑 黄宇
全部0条评论
快来发表一下你的评论吧 !