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