1# ServiceExtensionAbility 2<!--Kit: Ability Kit--> 3<!--Subsystem: Ability--> 4<!--Owner: @yewei0794--> 5<!--Designer: @jsjzju--> 6<!--Tester: @lixueqing513--> 7<!--Adviser: @huipeizi--> 8 9## 概述 10 11[ServiceExtensionAbility](../reference/apis-ability-kit/js-apis-app-ability-serviceExtensionAbility-sys.md)是SERVICE类型的[ExtensionAbility](../reference/apis-ability-kit/js-apis-app-ability-extensionAbility.md)组件,提供后台服务能力,其内部持有了一个[ServiceExtensionContext](../reference/apis-ability-kit/js-apis-inner-application-serviceExtensionContext-sys.md),通过ServiceExtensionContext提供了丰富的接口供外部使用。 12 13本文描述中称被启动的ServiceExtensionAbility为服务端,称启动ServiceExtensionAbility的组件为客户端。 14 15ServiceExtensionAbility可以被其他组件启动或连接,并根据调用者的请求信息在后台处理相关事务。ServiceExtensionAbility支持以启动和连接两种形式运行,系统应用可以调用[startServiceExtensionAbility()](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext-sys.md#startserviceextensionability)方法启动后台服务,也可以调用[connectServiceExtensionAbility()](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext.md#connectserviceextensionability)方法连接后台服务,而三方应用只能调用connectServiceExtensionAbility()方法连接后台服务。启动和连接后台服务的差别: 16 17- **启动**:AbilityA启动ServiceB,启动后AbilityA和ServiceB为弱关联,AbilityA退出后,ServiceB可以继续存在。 18 19- **连接**:AbilityA连接ServiceB,连接后AbilityA和ServiceB为强关联,AbilityA退出后,ServiceB也一起退出。 20 21此处有如下细节需要注意: 22 23- 若Service只通过connect的方式被拉起,那么该Service的生命周期将受客户端控制,当客户端调用一次connectServiceExtensionAbility()方法,将建立一个连接,当客户端退出或者调用[disconnectServiceExtensionAbility()](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext.md#disconnectserviceextensionability)方法,该连接将断开。当所有连接都断开后,Service将自动退出。 24 25- Service一旦通过start的方式被拉起,将不会自动退出,系统应用可以调用[stopServiceExtensionAbility()](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext-sys.md#stopserviceextensionability)方法将Service退出。 26 27- 只能在主线程线程中执行connect/disconnect操作,不要在Worker、TaskPool等子线程中执行connect/disconnect操作。 28 29> **说明:** 30> 31> 1. 当前不支持三方应用实现ServiceExtensionAbility。如果三方开发者想要实现后台处理相关事务的功能,可以使用后台任务,具体请参见[后台任务](../task-management/background-task-overview.md)。 32> 2. 三方应用的UIAbility组件可以通过Context连接系统提供的ServiceExtensionAbility。 33> 3. 三方应用需要在前台获焦的情况下才能连接系统提供的ServiceExtensionAbility。 34 35## 生命周期 36 37[ServiceExtensionAbility](../reference/apis-ability-kit/js-apis-app-ability-serviceExtensionAbility-sys.md)提供了[onCreate()](../reference/apis-ability-kit/js-apis-app-ability-serviceExtensionAbility-sys.md#oncreate)、[onRequest()](../reference/apis-ability-kit/js-apis-app-ability-serviceExtensionAbility-sys.md#onrequest)、[onConnect()](../reference/apis-ability-kit/js-apis-app-ability-serviceExtensionAbility-sys.md#onconnect)、[onDisconnect()](../reference/apis-ability-kit/js-apis-app-ability-serviceExtensionAbility-sys.md#ondisconnect)和[onDestroy()](../reference/apis-ability-kit/js-apis-app-ability-serviceExtensionAbility-sys.md#ondestroy)生命周期回调,根据需要重写对应的回调方法。下图展示了ServiceExtensionAbility的生命周期。 38 39 **图1** ServiceExtensionAbility生命周期 40 41 42- **onCreate** 43 服务被首次创建时触发该回调,开发者可以在此进行一些初始化的操作,例如注册公共事件监听等。 44 45 > **说明:** 46 > 如果服务已创建,再次启动该ServiceExtensionAbility不会触发onCreate()回调。 47 48- **onRequest** 49 当另一个组件调用[startServiceExtensionAbility()](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext-sys.md#startserviceextensionability)方法启动该服务组件时,触发该回调。执行此方法后,服务会启动并在后台运行。每调用一次startServiceExtensionAbility()方法均会触发该回调。 50 51- **onConnect** 52 当另一个组件调用[connectServiceExtensionAbility()](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext.md#connectserviceextensionability)方法与该服务连接时,触发该回调。开发者在此方法中,返回一个远端代理对象([IRemoteObject](../reference/apis-ipc-kit/js-apis-rpc.md#iremoteobject)),客户端拿到这个对象后可以通过这个对象与服务端进行RPC通信,同时系统侧也会将该远端代理对象(IRemoteObject)储存。后续若有组件再调用connectServiceExtensionAbility()方法,系统侧会直接将所保存的远端代理对象(IRemoteObject)返回,而不再触发该回调。 53 54- **onDisconnect** 55 当最后一个连接断开时,将触发该回调。客户端死亡或者调用[disconnectServiceExtensionAbility()](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext.md#disconnectserviceextensionability)方法可以使连接断开。 56 57- **onDestroy** 58 当不再使用服务且准备将其销毁该实例时,触发该回调。开发者可以在该回调中清理资源,如注销监听等。 59 60## 实现一个后台服务(仅对系统应用开放) 61 62### 开发准备 63 64只有系统应用才允许实现[ServiceExtensionAbility](../reference/apis-ability-kit/js-apis-app-ability-serviceExtensionAbility-sys.md),因此开发者在开发之前需做如下准备: 65 66- **替换Full SDK**:ServiceExtensionAbility相关接口都被标记为System-API,默认对开发者隐藏,因此需要手动从镜像站点获取Full SDK,并在DevEco Studio中替换,具体操作可参考[替换指南](../faqs/full-sdk-switch-guide.md)。 67 68- **申请AllowAppUsePrivilegeExtension特权**:只有具有AllowAppUsePrivilegeExtension特权的应用才允许开发ServiceExtensionAbility,具体申请方式可参考[应用特权配置指南](../../device-dev/subsystems/subsys-app-privilege-config-guide.md)。 69 70### 定义IDL接口 71 72ServiceExtensionAbility作为后台服务,需要向外部提供可调用的接口,开发者可将接口定义在idl文件中,并使用[IDL工具](../IDL/idl-guidelines.md)生成对应的proxy、stub文件。此处定义一个名为IIdlServiceExt.idl的文件作为示例: 73 74```cpp 75interface OHOS.IIdlServiceExt { 76 int ProcessData([in] int data); 77 void InsertDataToMap([in] String key, [in] int val); 78} 79``` 80 81在DevEco Studio工程Module对应的ets目录下手动新建名为IdlServiceExt的目录,将[IDL工具](../IDL/idl-guidelines.md)生成的文件复制到该目录下,并创建一个名为idl_service_ext_impl.ts的文件,作为idl接口的实现: 82 83``` 84├── ets 85│ ├── IdlServiceExt 86│ │ ├── i_idl_service_ext.ts # 生成文件 87│ │ ├── idl_service_ext_proxy.ts # 生成文件 88│ │ ├── idl_service_ext_stub.ts # 生成文件 89│ │ ├── idl_service_ext_impl.ts # 开发者自定义文件,对idl接口的具体实现 90│ └ 91└ 92``` 93 94idl_service_ext_impl.ts实现如下: 95 96```ts 97import IdlServiceExtStub from './idl_service_ext_stub'; 98import hilog from '@ohos.hilog'; 99import type { insertDataToMapCallback } from './i_idl_service_ext'; 100import type { processDataCallback } from './i_idl_service_ext'; 101 102const ERR_OK = 0; 103const TAG: string = "[IdlServiceExtImpl]"; 104const DOMAIN_NUMBER: number = 0xFF00; 105 106// 开发者需要在这个类型里对接口进行实现 107export default class ServiceExtImpl extends IdlServiceExtStub { 108 processData(data: number, callback: processDataCallback): void { 109 // 开发者自行实现业务逻辑 110 hilog.info(DOMAIN_NUMBER, TAG, `processData: ${data}`); 111 callback(ERR_OK, data + 1); // 鉴权通过,执行正常业务逻辑 112 } 113 114 insertDataToMap(key: string, val: number, callback: insertDataToMapCallback): void { 115 // 开发者自行实现业务逻辑 116 hilog.info(DOMAIN_NUMBER, TAG, `insertDataToMap, key: ${key} val: ${val}`); 117 callback(ERR_OK); 118 } 119} 120``` 121 122### 创建ServiceExtensionAbility 123 124在DevEco Studio工程中手动新建一个ServiceExtensionAbility,具体步骤如下: 125 1261. 在工程Module对应的ets目录下,右键选择“New > Directory”,新建一个目录并命名为ServiceExtAbility。 127 1282. 在ServiceExtAbility目录,右键选择“New > ArkTS File”,新建一个文件并命名为ServiceExtAbility.ets。 129 130 ``` 131 ├── ets 132 │ ├── IdlServiceExt 133 │ │ ├── i_idl_service_ext.ets # 生成文件 134 │ │ ├── idl_service_ext_proxy.ets # 生成文件 135 │ │ ├── idl_service_ext_stub.ets # 生成文件 136 │ │ ├── idl_service_ext_impl.ets # 开发者自定义文件,对idl接口的具体实现 137 │ ├── ServiceExtAbility 138 │ │ ├── ServiceExtAbility.ets 139 └ 140 ``` 141 1423. 在ServiceExtAbility.ets文件中,增加导入[ServiceExtensionAbility](../reference/apis-ability-kit/js-apis-app-ability-serviceExtensionAbility-sys.md)的依赖包,自定义类继承ServiceExtensionAbility并实现生命周期回调,在[onConnect](../reference/apis-ability-kit/js-apis-app-ability-serviceExtensionAbility-sys.md#oncreate)生命周期回调里,需要将之前定义的ServiceExtImpl对象返回。 143 144 ```ts 145 import { ServiceExtensionAbility, Want } from '@kit.AbilityKit'; 146 import { rpc } from '@kit.IPCKit'; 147 import { hilog } from '@kit.PerformanceAnalysisKit'; 148 import ServiceExtImpl from '../IdlServiceExt/idl_service_ext_impl'; 149 150 const TAG: string = '[ServiceExtAbility]'; 151 const DOMAIN_NUMBER: number = 0xFF00; 152 153 export default class ServiceExtAbility extends ServiceExtensionAbility { 154 serviceExtImpl: ServiceExtImpl = new ServiceExtImpl('ExtImpl'); 155 156 onCreate(want: Want): void { 157 let serviceExtensionContext = this.context; 158 hilog.info(DOMAIN_NUMBER, TAG, `onCreate, want: ${want.abilityName}`); 159 }; 160 161 onRequest(want: Want, startId: number): void { 162 hilog.info(DOMAIN_NUMBER, TAG, `onRequest, want: ${want.abilityName}`); 163 }; 164 165 onConnect(want: Want): rpc.RemoteObject { 166 hilog.info(DOMAIN_NUMBER, TAG, `onConnect, want: ${want.abilityName}`); 167 // 返回ServiceExtImpl对象,客户端获取后便可以与ServiceExtensionAbility进行通信 168 return this.serviceExtImpl as rpc.RemoteObject; 169 }; 170 171 onDisconnect(want: Want): void { 172 hilog.info(DOMAIN_NUMBER, TAG, `onDisconnect, want: ${want.abilityName}`); 173 }; 174 175 onDestroy(): void { 176 hilog.info(DOMAIN_NUMBER, TAG, 'onDestroy'); 177 }; 178 }; 179 ``` 180 1814. 在工程Module对应的[module.json5配置文件](../quick-start/module-configuration-file.md)中注册ServiceExtensionAbility,type标签需要设置为“service”,srcEntry标签表示当前ExtensionAbility组件所对应的代码路径。 182 183 ```json 184 { 185 "module": { 186 // ... 187 "extensionAbilities": [ 188 { 189 "name": "ServiceExtAbility", 190 "icon": "$media:icon", 191 "description": "service", 192 "type": "service", 193 "exported": true, 194 "srcEntry": "./ets/ServiceExtAbility/ServiceExtAbility.ets" 195 } 196 ] 197 } 198 } 199 ``` 200 201## 启动一个后台服务(仅对系统应用开放) 202 203系统应用通过[startServiceExtensionAbility()](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext-sys.md#startserviceextensionability)方法启动一个后台服务,服务的[onRequest()](../reference/apis-ability-kit/js-apis-app-ability-serviceExtensionAbility-sys.md#onrequest)回调就会被调用,并在该回调方法中接收到调用者传递过来的[Want](../reference/apis-ability-kit/js-apis-app-ability-want.md)对象。后台服务启动后,其生命周期独立于客户端,即使客户端已经销毁,该后台服务仍可继续运行。因此,后台服务需要在其工作完成时通过调用[ServiceExtensionContext](../reference/apis-ability-kit/js-apis-inner-application-serviceExtensionContext-sys.md)的[terminateSelf()](../reference/apis-ability-kit/js-apis-inner-application-serviceExtensionContext-sys.md#serviceextensioncontextterminateself)来自行停止,或者由另一个组件调用[stopServiceExtensionAbility()](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext-sys.md#stopserviceextensionability)来将其停止。 204 205> **说明:** 206> ServiceExtensionContext的startServiceExtensionAbility()、stopServiceExtensionAbility()和terminateSelf()为系统接口,三方应用不支持调用。 207 2081. 在系统应用中启动一个新的[ServiceExtensionAbility](../reference/apis-ability-kit/js-apis-app-ability-serviceExtensionAbility-sys.md)。示例中的context的获取方式请参见[获取UIAbility的上下文信息](uiability-usage.md#获取uiability的上下文信息)。 209 210 ```ts 211 import { common, Want } from '@kit.AbilityKit'; 212 import { hilog } from '@kit.PerformanceAnalysisKit'; 213 import { BusinessError } from '@kit.BasicServicesKit'; 214 215 const TAG: string = '[Page_ServiceExtensionAbility]'; 216 const DOMAIN_NUMBER: number = 0xFF00; 217 218 @Entry 219 @Component 220 struct Page_ServiceExtensionAbility { 221 build() { 222 Column() { 223 //... 224 List({ initialIndex: 0 }) { 225 ListItem() { 226 Row() { 227 //... 228 } 229 .onClick(() => { 230 let context = this.getUIContext().getHostContext() as common.UIAbilityContext; // UIAbilityContext 231 let want: Want = { 232 deviceId: '', 233 bundleName: 'com.samples.stagemodelabilitydevelop', 234 abilityName: 'ServiceExtAbility' 235 }; 236 context.startServiceExtensionAbility(want).then(() => { 237 hilog.info(DOMAIN_NUMBER, TAG, 'Succeeded in starting ServiceExtensionAbility.'); 238 // 成功启动后台服务 239 this.getUIContext().getPromptAction().showToast({ 240 message: 'SuccessfullyStartBackendService' 241 }); 242 }).catch((err: BusinessError) => { 243 hilog.error(DOMAIN_NUMBER, TAG, `Failed to start ServiceExtensionAbility. Code is ${err.code}, message is ${err.message}`); 244 }); 245 }) 246 } 247 //... 248 } 249 //... 250 } 251 //... 252 } 253 } 254 ``` 255 2562. 在系统应用中停止一个已启动的[ServiceExtensionAbility](../reference/apis-ability-kit/js-apis-app-ability-serviceExtensionAbility-sys.md)。 257 258 ```ts 259 import { common, Want } from '@kit.AbilityKit'; 260 import { hilog } from '@kit.PerformanceAnalysisKit'; 261 import { BusinessError } from '@kit.BasicServicesKit'; 262 263 const TAG: string = '[Page_ServiceExtensionAbility]'; 264 const DOMAIN_NUMBER: number = 0xFF00; 265 266 @Entry 267 @Component 268 struct Page_ServiceExtensionAbility { 269 build() { 270 Column() { 271 //... 272 List({ initialIndex: 0 }) { 273 ListItem() { 274 Row() { 275 //... 276 } 277 .onClick(() => { 278 let context = this.getUIContext().getHostContext() as common.UIAbilityContext; // UIAbilityContext 279 let want: Want = { 280 deviceId: '', 281 bundleName: 'com.samples.stagemodelabilitydevelop', 282 abilityName: 'ServiceExtAbility' 283 }; 284 context.stopServiceExtensionAbility(want).then(() => { 285 hilog.info(DOMAIN_NUMBER, TAG, 'Succeeded in stopping ServiceExtensionAbility.'); 286 this.getUIContext().getPromptAction().showToast({ 287 message: 'SuccessfullyStoppedAStartedBackendService' 288 }); 289 }).catch((err: BusinessError) => { 290 hilog.error(DOMAIN_NUMBER, TAG, `Failed to stop ServiceExtensionAbility. Code is ${err.code}, message is ${err.message}`); 291 }); 292 }) 293 } 294 //... 295 } 296 //... 297 } 298 //... 299 } 300 } 301 ``` 302 3033. 已启动的[ServiceExtensionAbility](../reference/apis-ability-kit/js-apis-app-ability-serviceExtensionAbility-sys.md)停止自身。 304 305 ```ts 306 import { common } from '@kit.AbilityKit'; 307 import { hilog } from '@kit.PerformanceAnalysisKit'; 308 import { BusinessError } from '@kit.BasicServicesKit'; 309 310 const TAG: string = '[Page_ServiceExtensionAbility]'; 311 const DOMAIN_NUMBER: number = 0xFF00; 312 313 @Entry 314 @Component 315 struct Page_ServiceExtensionAbility { 316 build() { 317 Column() { 318 //... 319 List({ initialIndex: 0 }) { 320 ListItem() { 321 Row() { 322 //... 323 } 324 .onClick(() => { 325 let context = this.getUIContext().getHostContext() as common.UIAbilityContext; // UIAbilityContext 326 context.terminateSelf().then(() => { 327 hilog.info(DOMAIN_NUMBER, TAG, 'Succeeded in terminating self.'); 328 // 成功停止当前后台服务 329 this.getUIContext().getPromptAction().showToast({ 330 message: 'SuccessfullyStopStartedBackendService' 331 }); 332 }).catch((err: BusinessError) => { 333 hilog.error(DOMAIN_NUMBER, TAG, `Failed to terminate self. Code is ${err.code}, message is ${err.message}`); 334 }); 335 }) 336 } 337 //... 338 } 339 //... 340 } 341 //... 342 } 343 } 344 ``` 345 346> **说明:** 347> 后台服务可以在后台长期运行,为了避免资源浪费,需要对后台服务的生命周期进行管理。即一个后台服务完成了请求方的任务,需要及时销毁。销毁已启动的后台服务有两种方式: 348> 349> - 后台服务自身调用[terminateSelf()](../reference/apis-ability-kit/js-apis-inner-application-serviceExtensionContext-sys.md#serviceextensioncontextterminateself)方法来自行停止。 350> - 由其他组件调用[stopServiceExtensionAbility()](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext-sys.md#stopserviceextensionability)方法来停止。 351> 调用terminateSelf()或stopServiceExtensionAbility()方法之后,系统将销毁后台服务。 352 353## 连接一个后台服务 354 355系统应用或者三方应用可以通过[connectServiceExtensionAbility()](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext.md#connectserviceextensionability)连接一个服务(在Want对象中指定启动的目标服务),服务的[onConnect()](../reference/apis-ability-kit/js-apis-app-ability-serviceExtensionAbility-sys.md#onconnect)就会被调用,并在该回调方法中接收到调用者传递过来的[Want](../reference/apis-ability-kit/js-apis-app-ability-want.md)对象,从而建立长连接。 356 357ServiceExtensionAbility服务组件在onConnect()中返回[IRemoteObject](../reference/apis-ipc-kit/js-apis-rpc.md#iremoteobject)对象,开发者通过该IRemoteObject定义通信接口,用于客户端与服务端进行RPC交互。多个客户端可以同时连接到同一个后台服务,客户端完成与服务的交互后,客户端需要通过调用[disconnectServiceExtensionAbility()](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext.md#disconnectserviceextensionability)来断开连接。如果所有连接到某个后台服务的客户端均已断开连接,则系统会销毁该服务。 358 359- 使用[connectServiceExtensionAbility()](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext.md#connectserviceextensionability)建立与后台服务的连接。示例中的context的获取方式请参见[获取UIAbility的上下文信息](uiability-usage.md#获取uiability的上下文信息)。 360 361 ```ts 362 import { common, Want } from '@kit.AbilityKit'; 363 import { rpc } from '@kit.IPCKit'; 364 import { hilog } from '@kit.PerformanceAnalysisKit'; 365 // 客户端需要将服务端对外提供的idl_service_ext_proxy.ts导入到本地工程中 366 import IdlServiceExtProxy from '../IdlServiceExt/idl_service_ext_proxy'; 367 368 const TAG: string = '[Page_ServiceExtensionAbility]'; 369 const DOMAIN_NUMBER: number = 0xFF00; 370 371 let connectionId: number; 372 let want: Want = { 373 deviceId: '', 374 bundleName: 'com.samples.stagemodelabilitydevelop', 375 abilityName: 'ServiceExtAbility' 376 }; 377 378 let options: common.ConnectOptions = { 379 onConnect(elementName, remote: rpc.IRemoteObject): void { 380 hilog.info(DOMAIN_NUMBER, TAG, 'onConnect callback'); 381 if (remote === null) { 382 hilog.info(DOMAIN_NUMBER, TAG, `onConnect remote is null`); 383 return; 384 } 385 let serviceExtProxy: IdlServiceExtProxy = new IdlServiceExtProxy(remote); 386 // 通过接口调用的方式进行通信,屏蔽了RPC通信的细节,简洁明了 387 serviceExtProxy.processData(1, (errorCode: number, retVal: number) => { 388 hilog.info(DOMAIN_NUMBER, TAG, `processData, errorCode: ${errorCode}, retVal: ${retVal}`); 389 }); 390 serviceExtProxy.insertDataToMap('theKey', 1, (errorCode: number) => { 391 hilog.info(DOMAIN_NUMBER, TAG, `insertDataToMap, errorCode: ${errorCode}`); 392 }) 393 }, 394 onDisconnect(elementName): void { 395 hilog.info(DOMAIN_NUMBER, TAG, 'onDisconnect callback'); 396 }, 397 onFailed(code: number): void { 398 hilog.info(DOMAIN_NUMBER, TAG, 'onFailed callback', JSON.stringify(code)); 399 } 400 }; 401 @Entry 402 @Component 403 struct Page_ServiceExtensionAbility { 404 build() { 405 Column() { 406 //... 407 List({ initialIndex: 0 }) { 408 ListItem() { 409 Row() { 410 //... 411 } 412 .onClick(() => { 413 let context = this.getUIContext().getHostContext() as common.UIAbilityContext; // UIAbilityContext 414 // 建立连接后返回的Id需要保存下来,在解绑服务时需要作为参数传入 415 connectionId = context.connectServiceExtensionAbility(want, options); 416 // 成功连接后台服务 417 this.getUIContext().getPromptAction().showToast({ 418 message: 'SuccessfullyConnectBackendService' 419 }); 420 // connectionId = context.connectAbility(want, options); 421 hilog.info(DOMAIN_NUMBER, TAG, `connectionId is : ${connectionId}`); 422 }) 423 } 424 //... 425 } 426 //... 427 } 428 //... 429 } 430 } 431 ``` 432 433- 使用[disconnectServiceExtensionAbility()](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext.md#disconnectserviceextensionability)断开与后台服务的连接。 434 435 ```ts 436 import { common } from '@kit.AbilityKit'; 437 import { hilog } from '@kit.PerformanceAnalysisKit'; 438 import { BusinessError } from '@kit.BasicServicesKit'; 439 440 const TAG: string = '[Page_ServiceExtensionAbility]'; 441 const DOMAIN_NUMBER: number = 0xFF00; 442 443 let connectionId: number; 444 @Entry 445 @Component 446 struct Page_ServiceExtensionAbility { 447 build() { 448 Column() { 449 //... 450 List({ initialIndex: 0 }) { 451 ListItem() { 452 Row() { 453 //... 454 } 455 .onClick(() => { 456 let context = this.getUIContext().getHostContext() as common.UIAbilityContext; // UIAbilityContext 457 // connectionId为调用connectServiceExtensionAbility接口时的返回值,需开发者自行维护 458 context.disconnectServiceExtensionAbility(connectionId).then(() => { 459 hilog.info(DOMAIN_NUMBER, TAG, 'disconnectServiceExtensionAbility success'); 460 // 成功断连后台服务 461 this.getUIContext().getPromptAction().showToast({ 462 message: 'SuccessfullyDisconnectBackendService' 463 }); 464 }).catch((error: BusinessError) => { 465 hilog.error(DOMAIN_NUMBER, TAG, 'disconnectServiceExtensionAbility failed'); 466 }); 467 }) 468 } 469 //... 470 } 471 //... 472 } 473 //... 474 } 475 } 476 ``` 477 478## 客户端与服务端通信 479 480客户端在[onConnect()](../reference/apis-ability-kit/js-apis-inner-ability-connectOptions.md#onconnect)中获取到[rpc.IRemoteObject](../reference/apis-ipc-kit/js-apis-rpc.md#iremoteobject)对象后便可与Service进行通信,有如下两种方式: 481 482- 使用服务端提供的IDL接口进行通信(推荐)。 483 484 ```ts 485 // 客户端需要将服务端对外提供的idl_service_ext_proxy.ts导入到本地工程中 486 import { common } from '@kit.AbilityKit'; 487 import { rpc } from '@kit.IPCKit'; 488 import { hilog } from '@kit.PerformanceAnalysisKit'; 489 import IdlServiceExtProxy from '../IdlServiceExt/idl_service_ext_proxy'; 490 491 const TAG: string = '[Page_ServiceExtensionAbility]'; 492 const DOMAIN_NUMBER: number = 0xFF00; 493 494 let options: common.ConnectOptions = { 495 onConnect(elementName, remote: rpc.IRemoteObject): void { 496 hilog.info(DOMAIN_NUMBER, TAG, 'onConnect callback'); 497 if (remote === null) { 498 hilog.info(DOMAIN_NUMBER, TAG, `onConnect remote is null`); 499 return; 500 } 501 let serviceExtProxy: IdlServiceExtProxy = new IdlServiceExtProxy(remote); 502 // 通过接口调用的方式进行通信,屏蔽了RPC通信的细节,简洁明了 503 serviceExtProxy.processData(1, (errorCode: number, retVal: number) => { 504 hilog.info(DOMAIN_NUMBER, TAG, `processData, errorCode: ${errorCode}, retVal: ${retVal}`); 505 }); 506 serviceExtProxy.insertDataToMap('theKey', 1, (errorCode: number) => { 507 hilog.info(DOMAIN_NUMBER, TAG, `insertDataToMap, errorCode: ${errorCode}`); 508 }) 509 }, 510 onDisconnect(elementName): void { 511 hilog.info(DOMAIN_NUMBER, TAG, 'onDisconnect callback'); 512 }, 513 onFailed(code: number): void { 514 hilog.info(DOMAIN_NUMBER, TAG, 'onFailed callback', JSON.stringify(code)); 515 } 516 }; 517 ``` 518 519- 直接使用[sendMessageRequest](../reference/apis-ipc-kit/js-apis-rpc.md#sendmessagerequest9)接口向服务端发送消息(不推荐)。 520 521 ```ts 522 import { common } from '@kit.AbilityKit'; 523 import { rpc } from '@kit.IPCKit'; 524 import { hilog } from '@kit.PerformanceAnalysisKit'; 525 import { BusinessError } from '@kit.BasicServicesKit'; 526 527 const TAG: string = '[Page_CollaborateAbility]'; 528 const DOMAIN_NUMBER: number = 0xFF00; 529 const REQUEST_CODE = 1; 530 let options: common.ConnectOptions = { 531 onConnect(elementName, remote): void { 532 hilog.info(DOMAIN_NUMBER, TAG, 'onConnect callback'); 533 if (remote === null) { 534 hilog.info(DOMAIN_NUMBER, TAG, `onConnect remote is null`); 535 return; 536 } 537 let option = new rpc.MessageOption(); 538 let data = new rpc.MessageSequence(); 539 let reply = new rpc.MessageSequence(); 540 541 data.writeInt(99); 542 // 开发者可发送data到目标端应用进行相应操作 543 // @param code 表示客户端发送的服务请求代码。 544 // @param data 表示客户端发送的{@link MessageSequence}对象。 545 // @param reply 表示远程服务发送的响应消息对象。 546 // @param options 指示操作是同步的还是异步的。 547 // @return 如果操作成功返回{@code true}; 否则返回 {@code false}。 548 549 remote.sendMessageRequest(REQUEST_CODE, data, reply, option).then((ret: rpc.RequestResult) => { 550 let errCode = reply.readInt(); // 在成功连接的情况下,会收到来自目标端返回的信息(100) 551 let msg: number = 0; 552 if (errCode === 0) { 553 msg = reply.readInt(); 554 } 555 // 成功连接后台服务 556 hilog.info(DOMAIN_NUMBER, TAG, `sendRequest success, msg: ${msg}`); 557 }).catch((error: BusinessError) => { 558 hilog.info(DOMAIN_NUMBER, TAG, `sendRequest failed, ${JSON.stringify(error)}`); 559 }); 560 }, 561 onDisconnect(elementName): void { 562 hilog.info(DOMAIN_NUMBER, TAG, 'onDisconnect callback'); 563 }, 564 onFailed(code): void { 565 hilog.info(DOMAIN_NUMBER, TAG, 'onFailed callback'); 566 } 567 }; 568 //... 569 ``` 570 571## 服务端对客户端身份校验 572 573部分开发者需要使用ServiceExtension提供一些较为敏感的服务,因此需要对客户端身份进行校验,开发者可在IDL接口的stub端进行校验,IDL接口实现详见上文[定义IDL接口](#定义idl接口),此处推荐两种校验方式: 574 575- **通过callerUid识别客户端应用** 576 577 通过调用[getCallingUid()](../reference/apis-ipc-kit/js-apis-rpc.md#getcallinguid)接口获取客户端的uid,再调用[getBundleNameByUid()](../reference/apis-ability-kit/js-apis-bundleManager-sys.md#bundlemanagergetbundlenamebyuid14)接口获取uid对应的bundleName,从而识别客户端身份。此处需要注意的是[getBundleNameByUid()](../reference/apis-ability-kit/js-apis-bundleManager-sys.md#bundlemanagergetbundlenamebyuid14)是一个异步接口,因此服务端无法将校验结果返回给客户端,这种校验方式适合客户端向服务端发起执行异步任务请求的场景,示例代码如下: 578 579 ```ts 580 import { bundleManager } from '@kit.AbilityKit'; 581 import { rpc } from '@kit.IPCKit'; 582 import { hilog } from '@kit.PerformanceAnalysisKit'; 583 import { BusinessError } from '@kit.BasicServicesKit'; 584 import IdlServiceExtStub from './idl_service_ext_stub'; 585 import type { InsertDataToMapCallback } from './i_idl_service_ext'; 586 import type { ProcessDataCallback } from './i_idl_service_ext'; 587 588 const ERR_OK = 0; 589 const ERR_DENY = -1; 590 const TAG: string = "[IdlServiceExtImpl]"; 591 const DOMAIN_NUMBER: number = 0xFF00; 592 593 // 开发者需要在这个类型里对接口进行实现 594 export default class ServiceExtImpl extends IdlServiceExtStub { 595 processData(data: number, callback: ProcessDataCallback): void { 596 // 开发者自行实现业务逻辑 597 hilog.info(DOMAIN_NUMBER, TAG, `processData: ${data}`); 598 let callerUid = rpc.IPCSkeleton.getCallingUid(); 599 bundleManager.getBundleNameByUid(callerUid).then((callerBundleName) => { 600 hilog.info(DOMAIN_NUMBER, TAG, 'getBundleNameByUid: ' + callerBundleName); 601 // 对客户端包名进行识别 602 if (callerBundleName !== 'com.samples.stagemodelabilitydevelop') { // 识别不通过 603 hilog.info(DOMAIN_NUMBER, TAG, 'The caller bundle is not in trustlist, reject'); 604 return; 605 } 606 // 识别通过,执行正常业务逻辑 607 }).catch((err: BusinessError) => { 608 hilog.info(DOMAIN_NUMBER, TAG, 'getBundleNameByUid failed: ' + err.message); 609 }); 610 //... 611 }; 612 613 insertDataToMap(key: string, val: number, callback: InsertDataToMapCallback): void { 614 // 开发者自行实现业务逻辑 615 hilog.info(DOMAIN_NUMBER, TAG, `insertDataToMap, key: ${key} val: ${val}`); 616 callback(ERR_OK); 617 }; 618 }; 619 ``` 620 621- **通过callerTokenId对客户端进行鉴权** 622 623 通过调用[getCallingTokenId()](../reference/apis-ipc-kit/js-apis-rpc.md#getcallingtokenid8)接口获取客户端的tokenID,再调用[verifyAccessTokenSync()](../reference/apis-ability-kit/js-apis-abilityAccessCtrl.md#verifyaccesstokensync9)接口判断客户端是否有某个具体权限,由于当前不支持自定义权限,因此只能校验当前[系统所定义的权限](../security/AccessToken/app-permissions.md)。示例代码如下: 624 625 ```ts 626 import { abilityAccessCtrl, bundleManager } from '@kit.AbilityKit'; 627 import { rpc } from '@kit.IPCKit'; 628 import { hilog } from '@kit.PerformanceAnalysisKit'; 629 import { BusinessError } from '@kit.BasicServicesKit'; 630 import IdlServiceExtStub from './idl_service_ext_stub'; 631 import type { InsertDataToMapCallback } from './i_idl_service_ext'; 632 import type { ProcessDataCallback } from './i_idl_service_ext'; 633 634 const ERR_OK = 0; 635 const ERR_DENY = -1; 636 const TAG: string = '[IdlServiceExtImpl]'; 637 const DOMAIN_NUMBER: number = 0xFF00; 638 639 // 开发者需要在这个类型里对接口进行实现 640 export default class ServiceExtImpl extends IdlServiceExtStub { 641 processData(data: number, callback: ProcessDataCallback): void { 642 // 开发者自行实现业务逻辑 643 hilog.info(DOMAIN_NUMBER, TAG, `processData: ${data}`); 644 645 let callerUid = rpc.IPCSkeleton.getCallingUid(); 646 bundleManager.getBundleNameByUid(callerUid).then((callerBundleName) => { 647 hilog.info(DOMAIN_NUMBER, TAG, 'getBundleNameByUid: ' + callerBundleName); 648 // 对客户端包名进行识别 649 if (callerBundleName !== 'com.samples.stagemodelabilitydevelop') { // 识别不通过 650 hilog.info(DOMAIN_NUMBER, TAG, 'The caller bundle is not in trustlist, reject'); 651 return; 652 } 653 // 识别通过,执行正常业务逻辑 654 }).catch((err: BusinessError) => { 655 hilog.info(DOMAIN_NUMBER, TAG, 'getBundleNameByUid failed: ' + err.message); 656 }); 657 658 let callerTokenId = rpc.IPCSkeleton.getCallingTokenId(); 659 let accessManger = abilityAccessCtrl.createAtManager(); 660 // 所校验的具体权限由开发者自行选择,此处ohos.permission.GET_BUNDLE_INFO_PRIVILEGED只作为示例 661 let grantStatus = accessManger.verifyAccessTokenSync(callerTokenId, 'ohos.permission.GET_BUNDLE_INFO_PRIVILEGED'); 662 if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_DENIED) { 663 hilog.info(DOMAIN_NUMBER, TAG, 'PERMISSION_DENIED'); 664 callback(ERR_DENY, data); // 鉴权失败,返回错误 665 return; 666 } 667 hilog.info(DOMAIN_NUMBER, TAG, 'verify access token success.'); 668 callback(ERR_OK, data + 1); // 鉴权通过,执行正常业务逻辑 669 }; 670 671 insertDataToMap(key: string, val: number, callback: InsertDataToMapCallback): void { 672 // 开发者自行实现业务逻辑 673 hilog.info(DOMAIN_NUMBER, TAG, `insertDataToMap, key: ${key} val: ${val}`); 674 callback(ERR_OK); 675 }; 676 }; 677 ``` 678 679## 相关实例 680 681针对ServiceExtensionAbility开发,有以下相关实例可供参考: 682 683- [Ability与ServiceExtensionAbility通信(ArkTS)(Full SDK)(API9)](https://gitcode.com/openharmony/applications_app_samples/tree/master/code/SystemFeature/IDL/AbilityConnectServiceExtension) 684 685- [Stage模型(ArkTS)(Full SDK)(API10)](https://gitcode.com/openharmony/applications_app_samples/tree/master/code/SystemFeature/ApplicationModels/StageModel) 686