• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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![ServiceExtensionAbility-lifecycle](figures/ServiceExtensionAbility-lifecycle.png)
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│ └
9192```
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 &gt; Directory”,新建一个目录并命名为ServiceExtAbility。
127
1282. 在ServiceExtAbility目录,右键选择“New &gt; ArkTS File”,新建一个文件并命名为ServiceExtAbility.ets129
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
139140    ```
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