• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 多端协同
2
3
4## 功能描述
5
6多端协同主要包括如下场景:
7
8- [通过跨设备启动UIAbility和ServiceExtensionAbility组件实现多端协同(无返回数据)](#通过跨设备启动uiability和serviceextensionability组件实现多端协同无返回数据)
9
10- [通过跨设备启动UIAbility组件实现多端协同(获取返回数据)](#通过跨设备启动uiability组件实现多端协同获取返回数据)
11
12- [通过跨设备连接ServiceExtensionAbility组件实现多端协同](#通过跨设备连接serviceextensionability组件实现多端协同)
13
14- [通过跨设备Call调用实现多端协同](#通过跨设备call调用实现多端协同)
15
16
17## 多端协同流程
18
19多端协同流程如下图所示。
20
21  **图1** 多端协同流程图
22
23![hop-multi-device-collaboration](figures/hop-multi-device-collaboration.png)
24
25
26## 约束限制
27
28- 由于“多端协同任务管理”能力尚未具备,开发者当前只能通过开发系统应用获取设备列表,不支持三方应用接入。
29
30- 多端协同需遵循[分布式跨设备组件启动规则](component-startup-rules.md#分布式跨设备组件启动规则)。
31
32- 为了获得最佳体验,使用[want](../reference/apis-ability-kit/js-apis-app-ability-want.md)传输的数据建议在100KB以下。
33
34
35## 通过跨设备启动UIAbility和ServiceExtensionAbility组件实现多端协同(无返回数据)
36
37在设备A上通过发起端应用提供的启动按钮,启动设备B上指定的[UIAbility](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md)与[ServiceExtensionAbility](../reference/apis-ability-kit/js-apis-app-ability-serviceExtensionAbility-sys.md)。
38
39
40### 接口说明
41
42  **表1** 跨设备启动API接口功能介绍
43
44| **接口名** | **描述** |
45| -------- | -------- |
46| startAbility(want: Want, callback: AsyncCallback<void>): void; | 启动UIAbility和ServiceExtensionAbility(callback形式)。 |
47| stopServiceExtensionAbility(want: Want, callback: AsyncCallback<void>): void; | 退出启动的ServiceExtensionAbility(callback形式)。 |
48| stopServiceExtensionAbility(want: Want): Promise<void>; | 退出启动的ServiceExtensionAbility(Promise形式)。 |
49
50
51### 开发步骤
52
531. 需要申请`ohos.permission.DISTRIBUTED_DATASYNC`权限,配置方式请参见[声明权限](../security/AccessToken/declare-permissions.md)。
54
552. 同时需要在应用首次启动时弹窗向用户申请授权,使用方式请参见[向用户申请授权](../security/AccessToken/request-user-authorization.md)。
56
573. 获取目标设备的设备ID。
58
59    ```ts
60    import { distributedDeviceManager } from '@kit.DistributedServiceKit';
61    import { hilog } from '@kit.PerformanceAnalysisKit';
62
63    const TAG: string = '[Page_CollaborateAbility]';
64    const DOMAIN_NUMBER: number = 0xFF00;
65
66    let dmClass: distributedDeviceManager.DeviceManager;
67
68    function initDmClass(): void {
69      // 其中createDeviceManager接口为系统API
70      try {
71        dmClass = distributedDeviceManager.createDeviceManager('com.samples.stagemodelabilitydevelop');
72        hilog.info(DOMAIN_NUMBER, TAG, JSON.stringify(dmClass) ?? '');
73      } catch (err) {
74        hilog.error(DOMAIN_NUMBER, TAG, 'createDeviceManager err: ' + JSON.stringify(err));
75      }
76    }
77
78    function getRemoteDeviceId(): string | undefined {
79      if (typeof dmClass === 'object' && dmClass !== null) {
80        let list = dmClass.getAvailableDeviceListSync();
81        hilog.info(DOMAIN_NUMBER, TAG, JSON.stringify(dmClass), JSON.stringify(list));
82        if (typeof (list) === 'undefined' || typeof (list.length) === 'undefined') {
83          hilog.error(DOMAIN_NUMBER, TAG, 'getRemoteDeviceId err: list is null');
84          return;
85        }
86        if (list.length === 0) {
87          hilog.error(DOMAIN_NUMBER, TAG, `getRemoteDeviceId err: list is empty`);
88          return;
89        }
90        return list[0].networkId;
91      } else {
92        hilog.error(DOMAIN_NUMBER, TAG, 'getRemoteDeviceId err: dmClass is null');
93        return;
94      }
95    }
96    ```
97
984. 设置目标组件参数,调用[startAbility()](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext.md#startability)接口,启动[UIAbility](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md)或[ServiceExtensionAbility](../reference/apis-ability-kit/js-apis-app-ability-serviceExtensionAbility-sys.md)。
99
100    ```ts
101    import { BusinessError } from '@kit.BasicServicesKit';
102    import { hilog } from '@kit.PerformanceAnalysisKit';
103    import { Want, common } from '@kit.AbilityKit';
104    import { distributedDeviceManager } from '@kit.DistributedServiceKit';
105    import { promptAction } from '@kit.ArkUI';
106
107    const TAG: string = '[Page_CollaborateAbility]';
108    const DOMAIN_NUMBER: number = 0xFF00;
109    let dmClass: distributedDeviceManager.DeviceManager;
110
111    function getRemoteDeviceId(): string | undefined {
112      if (typeof dmClass === 'object' && dmClass !== null) {
113        let list = dmClass.getAvailableDeviceListSync();
114        hilog.info(DOMAIN_NUMBER, TAG, JSON.stringify(dmClass), JSON.stringify(list));
115        if (typeof (list) === 'undefined' || typeof (list.length) === 'undefined') {
116          hilog.error(DOMAIN_NUMBER, TAG, 'getRemoteDeviceId err: list is null');
117          return;
118        }
119        if (list.length === 0) {
120          hilog.error(DOMAIN_NUMBER, TAG, `getRemoteDeviceId err: list is empty`);
121          return;
122        }
123        return list[0].networkId;
124      } else {
125        hilog.error(DOMAIN_NUMBER, TAG, 'getRemoteDeviceId err: dmClass is null');
126        return;
127      }
128    };
129
130    @Entry
131    @Component
132    struct Page_CollaborateAbility {
133      private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
134      build() {
135        Column() {
136          //...
137          List({ initialIndex: 0 }) {
138            //...
139            ListItem() {
140              Row() {
141                //...
142              }
143              .onClick(() => {
144                let want: Want = {
145                  deviceId: getRemoteDeviceId(),
146                  bundleName: 'com.samples.stagemodelabilityinteraction',
147                  abilityName: 'CollaborateAbility',
148                  moduleName: 'entry' // moduleName非必选
149                };
150                // context为发起端UIAbility的AbilityContext
151                this.context.startAbility(want).then(() => {
152                  promptAction.openToast({
153                    message: 'SuccessfulCollaboration'
154                  });
155                }).catch((err: BusinessError) => {
156                  hilog.error(DOMAIN_NUMBER, TAG, `startAbility err: ` + JSON.stringify(err));
157                });
158              })
159            }
160            //...
161          }
162          //...
163        }
164        //...
165      }
166    }
167    ```
168
1695. 当设备A发起端应用不需要设备B上的[ServiceExtensionAbility](../reference/apis-ability-kit/js-apis-app-ability-serviceExtensionAbility-sys.md)时,可调用[stopServiceExtensionAbility](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext-sys.md#stopserviceextensionability-1)接口退出。(该接口不支持[UIAbility](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md)的退出,UIAbility由用户手动通过任务管理退出)
170
171    ```ts
172    import { BusinessError } from '@kit.BasicServicesKit';
173    import { hilog } from '@kit.PerformanceAnalysisKit';
174    import { Want, common } from '@kit.AbilityKit';
175    import { distributedDeviceManager } from '@kit.DistributedServiceKit';
176
177    const TAG: string = '[Page_CollaborateAbility]';
178    const DOMAIN_NUMBER: number = 0xFF00;
179    let dmClass: distributedDeviceManager.DeviceManager;
180
181    function getRemoteDeviceId(): string | undefined {
182      if (typeof dmClass === 'object' && dmClass !== null) {
183        let list = dmClass.getAvailableDeviceListSync();
184        hilog.info(DOMAIN_NUMBER, TAG, JSON.stringify(dmClass), JSON.stringify(list));
185        if (typeof (list) === 'undefined' || typeof (list.length) === 'undefined') {
186          hilog.error(DOMAIN_NUMBER, TAG, 'getRemoteDeviceId err: list is null');
187          return;
188        }
189        if (list.length === 0) {
190          hilog.error(DOMAIN_NUMBER, TAG, `getRemoteDeviceId err: list is empty`);
191          return;
192        }
193        return list[0].networkId;
194      } else {
195        hilog.error(DOMAIN_NUMBER, TAG, 'getRemoteDeviceId err: dmClass is null');
196        return;
197      }
198    };
199
200    @Entry
201    @Component
202    struct Page_CollaborateAbility {
203      private context = this.getUIContext().getHostContext();
204
205      build() {
206        // ...
207        Button('stopServiceExtensionAbility')
208          .onClick(() => {
209            let want: Want = {
210              deviceId: getRemoteDeviceId(),
211              bundleName: 'com.example.myapplication',
212              abilityName: 'FuncAbility',
213              moduleName: 'module1', // moduleName非必选
214            }
215            // 退出由startAbility接口启动的ServiceExtensionAbility
216            this.context.stopServiceExtensionAbility(want).then(() => {
217              hilog.info(DOMAIN_NUMBER, TAG, "stop service extension ability success")
218            }).catch((err: BusinessError) => {
219              hilog.error(DOMAIN_NUMBER, TAG, `stop service extension ability err is ` + JSON.stringify(err));
220            })
221          })
222      }
223    }
224    ```
225
226## 通过跨设备启动UIAbility组件实现多端协同(获取返回数据)
227
228在设备A上通过应用提供的启动按钮,启动设备B上指定的[UIAbility](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md),当设备B上的UIAbility退出后,会将返回值发回设备A上的发起端应用。
229
230
231### 接口说明
232
233  **表2** 跨设备启动,返回结果数据API接口功能描述
234
235| 接口名 | 描述 |
236| -------- | -------- |
237| startAbilityForResult(want: Want, callback: AsyncCallback<AbilityResult>): void; | 启动UIAbility并在该Ability退出的时候返回执行结果(callback形式)。 |
238| terminateSelfWithResult(parameter: AbilityResult, callback: AsyncCallback<void>): void; | 停止UIAbility,配合startAbilityForResult使用,返回给接口调用方AbilityResult信息(callback形式)。 |
239| terminateSelfWithResult(parameter: AbilityResult): Promise<void>; | 停止UIAbility,配合startAbilityForResult使用,返回给接口调用方AbilityResult信息(promise形式)。 |
240
241
242### 开发步骤
243
2441. 需要申请`ohos.permission.DISTRIBUTED_DATASYNC`权限,配置方式请参见[声明权限](../security/AccessToken/declare-permissions.md)。
245
2462. 同时需要在应用首次启动时弹窗向用户申请授权,使用方式请参见[向用户申请授权](../security/AccessToken/request-user-authorization.md)。
247
2483. 在发起端设置目标组件参数,调用[startAbilityForResult()](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext.md#startabilityforresult)接口启动目标端UIAbility,异步回调中的data用于接收目标端UIAbility停止自身后返回给调用方UIAbility的信息。getRemoteDeviceId方法参照[通过跨设备启动uiability和serviceextensionability组件实现多端协同无返回数据](#通过跨设备启动uiability和serviceextensionability组件实现多端协同无返回数据)。
249
250    ```ts
251    import { BusinessError } from '@kit.BasicServicesKit';
252    import { hilog } from '@kit.PerformanceAnalysisKit';
253    import { Want, common } from '@kit.AbilityKit';
254    import { distributedDeviceManager } from '@kit.DistributedServiceKit';
255    import { promptAction } from '@kit.ArkUI';
256
257    const DOMAIN_NUMBER: number = 0xFF00;
258    const TAG: string = '[Page_CollaborateAbility]';
259    let dmClass: distributedDeviceManager.DeviceManager;
260
261    function getRemoteDeviceId(): string | undefined {
262      if (typeof dmClass === 'object' && dmClass !== null) {
263        let list = dmClass.getAvailableDeviceListSync();
264        hilog.info(DOMAIN_NUMBER, TAG, JSON.stringify(dmClass), JSON.stringify(list));
265        if (typeof (list) === 'undefined' || typeof (list.length) === 'undefined') {
266          hilog.error(DOMAIN_NUMBER, TAG, 'getRemoteDeviceId err: list is null');
267          return;
268        }
269        if (list.length === 0) {
270          hilog.error(DOMAIN_NUMBER, TAG, `getRemoteDeviceId err: list is empty`);
271          return;
272        }
273        return list[0].networkId;
274      } else {
275        hilog.error(DOMAIN_NUMBER, TAG, 'getRemoteDeviceId err: dmClass is null');
276        return;
277      }
278    };
279
280    @Entry
281    @Component
282    struct Page_CollaborateAbility {
283      private context = this.getUIContext().getHostContext();
284      build() {
285        Column() {
286          //...
287          List({ initialIndex: 0 }) {
288            //...
289            ListItem() {
290              Row() {
291                //...
292              }
293              .onClick(() => {
294                let want: Want = {
295                  deviceId: getRemoteDeviceId(),
296                  bundleName: 'com.samples.stagemodelabilityinteraction',
297                  abilityName: 'ServiceExtAbility',
298                  moduleName: 'entry' // moduleName非必选
299                };
300                // 退出由startAbility接口启动的ServiceExtensionAbility
301                this.context.stopServiceExtensionAbility(want).then(() => {
302                  hilog.info(DOMAIN_NUMBER, TAG, 'stop service extension ability success')
303                  promptAction.openToast({
304                    message: 'SuccessfullyStop'
305                  });
306                }).catch((err: BusinessError) => {
307                  hilog.error(DOMAIN_NUMBER, TAG, `stop service extension ability err is ` + JSON.stringify(err));
308                });
309              })
310            }
311            //...
312          }
313          //...
314        }
315        //...
316      }
317    }
318    ```
319
3204. 在目标端[UIAbility](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md)任务完成后,调用[terminateSelfWithResult()](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext.md#terminateselfwithresult)方法,将数据返回给发起端的UIAbility。
321
322    ```ts
323    import { common } from '@kit.AbilityKit';
324    import { hilog } from '@kit.PerformanceAnalysisKit';
325    import { BusinessError } from '@kit.BasicServicesKit';
326
327    const TAG: string = '[Page_CollaborateAbility]';
328    const DOMAIN_NUMBER: number = 0xFF00;
329
330    @Entry
331    @Component
332    struct Page_CollaborateAbility {
333      private context = this.getUIContext().getHostContext();
334
335      build() {
336        Column() {
337          //...
338          List({ initialIndex: 0 }) {
339            //...
340            ListItem() {
341              Row() {
342                //...
343              }
344              .onClick(() => {
345                const RESULT_CODE: number = 1001;
346                // context为目标端UIAbility的AbilityContext
347                this.context.terminateSelfWithResult(
348                  {
349                    resultCode: RESULT_CODE,
350                    want: {
351                      bundleName: 'ohos.samples.stagemodelabilitydevelop',
352                      abilityName: 'CollaborateAbility',
353                      moduleName: 'entry',
354                      parameters: {
355                        info: '来自Page_CollaborateAbility页面'
356                      }
357                    }
358                  },
359                  (err: BusinessError) => {
360                    hilog.error(DOMAIN_NUMBER, TAG, `terminateSelfWithResult err: ` + JSON.stringify(err));
361                  });
362              })
363            }
364            //...
365          }
366          //...
367        }
368        //...
369      }
370    }
371    ```
372
3735. 发起端[UIAbility](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md)接收到目标端UIAbility返回的信息,对其进行处理。
374
375    ```ts
376    import { BusinessError } from '@kit.BasicServicesKit';
377    import { hilog } from '@kit.PerformanceAnalysisKit';
378    import { Want, common } from '@kit.AbilityKit';
379    import { distributedDeviceManager } from '@kit.DistributedServiceKit';
380    import { promptAction } from '@kit.ArkUI';
381
382    const TAG: string = '[Page_CollaborateAbility]';
383    const DOMAIN_NUMBER: number = 0xFF00;
384    let dmClass: distributedDeviceManager.DeviceManager;
385
386    function getRemoteDeviceId(): string | undefined {
387      if (typeof dmClass === 'object' && dmClass !== null) {
388        let list = dmClass.getAvailableDeviceListSync();
389        hilog.info(DOMAIN_NUMBER, TAG, JSON.stringify(dmClass), JSON.stringify(list));
390        if (typeof (list) === 'undefined' || typeof (list.length) === 'undefined') {
391          hilog.error(DOMAIN_NUMBER, TAG, 'getRemoteDeviceId err: list is null');
392          return;
393        }
394        if (list.length === 0) {
395          hilog.error(DOMAIN_NUMBER, TAG, `getRemoteDeviceId err: list is empty`);
396          return;
397        }
398        return list[0].networkId;
399      } else {
400        hilog.error(DOMAIN_NUMBER, TAG, 'getRemoteDeviceId err: dmClass is null');
401        return;
402      }
403    };
404
405    @Entry
406    @Component
407    struct Page_CollaborateAbility {
408      private context = this.getUIContext().getHostContext();
409      build() {
410        Column() {
411          //...
412          List({ initialIndex: 0 }) {
413            //...
414            ListItem() {
415              Row() {
416                //...
417              }
418              .onClick(() => {
419                let want: Want = {
420                  deviceId: getRemoteDeviceId(),
421                  bundleName: 'com.samples.stagemodelabilityinteraction',
422                  abilityName: 'CollaborateAbility',
423                  moduleName: 'entry' // moduleName非必选
424                };
425                const RESULT_CODE: number = 1001;
426                // context为调用方UIAbility的UIAbilityContext
427                this.context.startAbilityForResult(want).then((data) => {
428                  if (data?.resultCode === RESULT_CODE) {
429                    // 解析目标端UIAbility返回的信息
430                    let info = data.want?.parameters?.info;
431                    hilog.info(DOMAIN_NUMBER, TAG, JSON.stringify(info) ?? '');
432                    if (info !== null) {
433                      promptAction.openToast({
434                        message: JSON.stringify(info)
435                      });
436                    }
437                  }
438                }).catch((error: BusinessError) => {
439                  hilog.error(DOMAIN_NUMBER, TAG, `startAbilityForResult err: ` + JSON.stringify(error));
440                });
441              })
442            }
443            //...
444          }
445          //...
446        }
447        //...
448      }
449    }
450    ```
451
452
453## 通过跨设备连接ServiceExtensionAbility组件实现多端协同
454
455系统应用可以通过[connectServiceExtensionAbility()](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext.md#connectserviceextensionability)跨设备连接一个服务,实现跨设备远程调用。比如:分布式游戏场景,平板作为遥控器,智慧屏作为显示器。
456
457
458### 接口说明
459
460  **表3** 跨设备连接API接口功能介绍
461
462| 接口名 | 描述 |
463| -------- | -------- |
464| connectServiceExtensionAbility(want: Want, options: ConnectOptions): number; | 连接ServiceExtensionAbility。 |
465| disconnectServiceExtensionAbility(connection: number, callback:AsyncCallback<void>): void; | 断开连接(callback形式)。 |
466| disconnectServiceExtensionAbility(connection: number): Promise<void>; | 断开连接(promise形式)。 |
467
468
469### 开发步骤
470
4711. 需要申请`ohos.permission.DISTRIBUTED_DATASYNC`权限,配置方式请参见[声明权限](../security/AccessToken/declare-permissions.md)。
472
4732. 同时需要在应用首次启动时弹窗向用户申请授权,使用方式请参见[向用户申请授权](../security/AccessToken/request-user-authorization.md)。
474
4753. 如果已有后台服务,请直接进入下一步;如果没有,则[实现一个后台服务(仅对系统应用开放)](serviceextensionability.md#实现一个后台服务仅对系统应用开放)。
476
4774. 连接一个后台服务。
478   - 实现IAbilityConnection接口。IAbilityConnection提供了以下方法供开发者实现:[onConnect()](../reference/apis-ability-kit/js-apis-inner-ability-connectOptions.md#onconnect)是用来处理连接Service成功的回调,[onDisconnect()](../reference/apis-ability-kit/js-apis-inner-ability-connectOptions.md#ondisconnect)是用来处理Service异常终止的回调,[onFailed()](../reference/apis-ability-kit/js-apis-inner-ability-connectOptions.md#onfailed)是用来处理连接Service失败的回调。
479   - 设置目标组件参数,包括目标设备ID、Bundle名称、Ability名称。
480   - 调用connectServiceExtensionAbility()发起连接。
481   - 连接成功,收到目标设备返回的服务句柄。
482   - 进行跨设备调用,获得目标端服务返回的结果。
483
484    ```ts
485    import { BusinessError } from '@kit.BasicServicesKit';
486    import { hilog } from '@kit.PerformanceAnalysisKit';
487    import { Want, common } from '@kit.AbilityKit';
488    import { distributedDeviceManager } from '@kit.DistributedServiceKit';
489    import { rpc } from '@kit.IPCKit';
490
491    const TAG: string = '[Page_CollaborateAbility]';
492    const DOMAIN_NUMBER: number = 0xFF00;
493    const REQUEST_CODE = 1;
494    let dmClass: distributedDeviceManager.DeviceManager;
495    let connectionId: number;
496    let options: common.ConnectOptions = {
497      onConnect(elementName, remote): void {
498        hilog.info(DOMAIN_NUMBER, TAG, 'onConnect callback');
499        if (remote === null) {
500          hilog.info(DOMAIN_NUMBER, TAG, `onConnect remote is null`);
501          return;
502        }
503        let option = new rpc.MessageOption();
504        let data = new rpc.MessageSequence();
505        let reply = new rpc.MessageSequence();
506        data.writeInt(99); // 开发者可发送data到目标端应用进行相应操作
507        // @param code 表示客户端发送的服务请求代码。
508        // @param data 表示客户端发送的{@link MessageSequence}对象。
509        // @param reply 表示远程服务发送的响应消息对象。
510        // @param options 指示操作是同步的还是异步的。
511        //
512        // @return 如果操作成功返回{@code true}; 否则返回 {@code false}。
513        remote.sendMessageRequest(REQUEST_CODE, data, reply, option).then((ret: rpc.RequestResult) => {
514          let errCode = reply.readInt(); // 在成功连接的情况下,会收到来自目标端返回的信息(100)
515          let msg: number = 0;
516          if (errCode === 0) {
517            msg = reply.readInt();
518          }
519          // 成功连接后台服务
520          hilog.info(DOMAIN_NUMBER, TAG, `sendRequest msg:${msg}`);
521        }).catch((error: BusinessError) => {
522          hilog.info(DOMAIN_NUMBER, TAG, `sendRequest failed, ${JSON.stringify(error)}`);
523        });
524      },
525      onDisconnect(elementName): void {
526        hilog.info(DOMAIN_NUMBER, TAG, 'onDisconnect callback');
527      },
528      onFailed(code): void {
529        hilog.info(DOMAIN_NUMBER, TAG, 'onFailed callback');
530      }
531    };
532
533    function getRemoteDeviceId(): string | undefined {
534      if (typeof dmClass === 'object' && dmClass !== null) {
535        let list = dmClass.getAvailableDeviceListSync();
536        hilog.info(DOMAIN_NUMBER, TAG, JSON.stringify(dmClass), JSON.stringify(list));
537        if (typeof (list) === 'undefined' || typeof (list.length) === 'undefined') {
538          hilog.error(DOMAIN_NUMBER, TAG, 'getRemoteDeviceId err: list is null');
539          return;
540        }
541        if (list.length === 0) {
542          hilog.error(DOMAIN_NUMBER, TAG, `getRemoteDeviceId err: list is empty`);
543          return;
544        }
545        return list[0].networkId;
546      } else {
547        hilog.error(DOMAIN_NUMBER, TAG, 'getRemoteDeviceId err: dmClass is null');
548        return;
549      }
550    }
551
552    @Entry
553    @Component
554    struct Page_CollaborateAbility {
555      private context = this.getUIContext().getHostContext();
556
557      build() {
558        Column() {
559          //...
560          List({ initialIndex: 0 }) {
561            //...
562            ListItem() {
563              Row() {
564                //...
565              }
566              .onClick(() => {
567                let want: Want = {
568                  'deviceId': getRemoteDeviceId(),
569                  'bundleName': 'com.samples.stagemodelabilityinteraction',
570                  'abilityName': 'ServiceExtAbility'
571                };
572                // 建立连接后返回的Id需要保存下来,在解绑服务时需要作为参数传入
573                connectionId = this.context.connectServiceExtensionAbility(want, options);
574              })
575            }
576            //...
577          }
578          //...
579        }
580        //...
581      }
582    }
583    ```
584
585    getRemoteDeviceId方法参照[通过跨设备启动uiability和serviceextensionability组件实现多端协同无返回数据](#通过跨设备启动uiability和serviceextensionability组件实现多端协同无返回数据)。
586
5875. 断开连接。调用[disconnectServiceExtensionAbility()](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext.md#disconnectserviceextensionability)断开与后台服务的连接。
588
589    ```ts
590    import { BusinessError } from '@kit.BasicServicesKit';
591    import { hilog } from '@kit.PerformanceAnalysisKit';
592    import { common } from '@kit.AbilityKit';
593    import { promptAction } from '@kit.ArkUI';
594
595    let connectionId: number;
596    const TAG: string = '[Page_CollaborateAbility]';
597    const DOMAIN_NUMBER: number = 0xFF00;
598
599    @Entry
600    @Component
601    struct Page_CollaborateAbility {
602      private context = this.getUIContext().getHostContext();
603      build() {
604        Column() {
605          //...
606          List({ initialIndex: 0 }) {
607            //...
608            ListItem() {
609              Row() {
610                //...
611              }
612              .onClick(() => {
613                this.context.disconnectServiceExtensionAbility(connectionId).then(() => {
614                  hilog.info(DOMAIN_NUMBER, TAG, 'disconnectServiceExtensionAbility success');
615                  // 成功断连后台服务
616                  promptAction.openToast({
617                    message: 'SuccessfullyDisconnectBackendService'
618                  })
619                }).catch((error: BusinessError) => {
620                  hilog.error(DOMAIN_NUMBER, TAG, 'disconnectServiceExtensionAbility failed');
621                });
622              })
623            }
624            //...
625          }
626          //...
627        }
628        //...
629      }
630    }
631    ```
632
633
634## 通过跨设备Call调用实现多端协同
635
636跨设备Call调用的基本原理与设备内Call调用相同,请参见[通过Call调用实现UIAbility交互(仅对系统应用开放)](uiability-intra-device-interaction.md#通过call调用实现uiability交互仅对系统应用开放)。
637
638下面介绍跨设备Call调用实现多端协同的方法。
639
640
641### 接口说明
642
643  **表4** Call API接口功能介绍
644
645| 接口名 | 描述 |
646| -------- | -------- |
647| startAbilityByCall(want: Want): Promise<Caller>; | 启动指定UIAbility至前台或后台,同时获取其Caller通信接口,调用方可使用Caller与被启动的Ability进行通信。 |
648| on(method: string, callback: CalleeCallBack): void | 通用组件Callee注册method对应的callback方法。 |
649| off(method: string): void | 通用组件Callee解注册method的callback方法。 |
650| call(method: string, data: rpc.Parcelable): Promise<void> | 向通用组件Callee发送约定序列化数据。 |
651| callWithResult(method: string, data: rpc.Parcelable): Promise<rpc.MessageSequence> | 向通用组件Callee发送约定序列化数据, 并将Callee返回的约定序列化数据带回。 |
652| release(): void | 释放通用组件的Caller通信接口。 |
653| on(type: "release", callback: OnReleaseCallback): void | 注册通用组件通信断开监听通知。 |
654
655
656### 开发步骤
657
6581. 需要申请`ohos.permission.DISTRIBUTED_DATASYNC`权限,配置方式请参见[声明权限](../security/AccessToken/declare-permissions.md)。
659
6602. 同时需要在应用首次启动时弹窗向用户申请授权,使用方式请参见[向用户申请授权](../security/AccessToken/request-user-authorization.md)。
661
6623. 创建被调用端UIAbility。
663
664     被调用端[UIAbility](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md)需要实现指定方法的数据接收回调函数、数据的序列化及反序列化方法。在需要接收数据期间,通过[on](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#on)接口注册监听,无需接收数据时通过[off](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#off)接口解除监听。
665
666    1. 配置UIAbility的启动模式。
667        配置[module.json5](../quick-start/module-configuration-file.md),将CalleeAbility配置为单实例"singleton"。
668
669        | Json字段 | 字段说明 |
670        | -------- | -------- |
671        | “launchType” | Ability的启动模式,设置为"singleton"类型。 |
672
673        UIAbility配置标签示例如下:
674
675        ```json
676        "abilities":[{
677            "name": ".CalleeAbility",
678            "srcEntry": "./ets/CalleeAbility/CalleeAbility.ets",
679            "launchType": "singleton",
680            "description": "$string:CalleeAbility_desc",
681            "icon": "$media:icon",
682            "label": "$string:CalleeAbility_label",
683            "exported": true
684        }]
685        ```
686    2. 导入UIAbility模块。
687
688        ```ts
689        import { UIAbility } from '@kit.AbilityKit';
690        ```
691    3. 定义约定的序列化数据。
692        调用端及被调用端发送接收的数据格式需协商一致,如下示例约定数据由number和string组成。
693
694
695        ```ts
696        import { rpc } from '@kit.IPCKit';
697
698        class MyParcelable {
699          num: number = 0;
700          str: string = '';
701
702          constructor(num: number, string: string) {
703            this.num = num;
704            this.str = string;
705          }
706
707          mySequenceable(num: number, string: string): void {
708            this.num = num;
709            this.str = string;
710          }
711
712          marshalling(messageSequence: rpc.MessageSequence): boolean {
713            messageSequence.writeInt(this.num);
714            messageSequence.writeString(this.str);
715            return true;
716          };
717
718          unmarshalling(messageSequence: rpc.MessageSequence): boolean {
719            this.num = messageSequence.readInt();
720            this.str = messageSequence.readString();
721            return true;
722          };
723        }
724        ```
725    4. 实现[Callee.on](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#on)监听及[Callee.off](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#off)解除监听。
726          如下示例在Ability的[onCreate](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#oncreate)注册MSG_SEND_METHOD监听,在[onDestroy](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#ondestroy)取消监听,收到序列化数据后作相应处理并返回。应用开发者根据实际业务需要做相应处理。
727
728        ```ts
729        import { AbilityConstant, UIAbility, Want, Caller } from '@kit.AbilityKit';
730        import { hilog } from '@kit.PerformanceAnalysisKit';
731        import { rpc } from '@kit.IPCKit';
732
733
734        const TAG: string = '[CalleeAbility]';
735        const MSG_SEND_METHOD: string = 'CallSendMsg';
736        const DOMAIN_NUMBER: number = 0xFF00;
737
738        class MyParcelable {
739          num: number = 0;
740          str: string = '';
741
742          constructor(num: number, string: string) {
743            this.num = num;
744            this.str = string;
745          };
746
747          mySequenceable(num: number, string: string): void {
748            this.num = num;
749            this.str = string;
750          };
751
752          marshalling(messageSequence: rpc.MessageSequence): boolean {
753            messageSequence.writeInt(this.num);
754            messageSequence.writeString(this.str);
755            return true;
756          };
757
758          unmarshalling(messageSequence: rpc.MessageSequence): boolean {
759            this.num = messageSequence.readInt();
760            this.str = messageSequence.readString();
761            return true;
762          };
763        }
764
765        function sendMsgCallback(data: rpc.MessageSequence): rpc.Parcelable {
766          hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', 'CalleeSortFunc called');
767
768          // 获取Caller发送的序列化数据
769          let receivedData: MyParcelable = new MyParcelable(0, '');
770          data.readParcelable(receivedData);
771          hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', `receiveData[${receivedData.num}, ${receivedData.str}]`);
772          let num: number = receivedData.num;
773
774          // 作相应处理
775          // 返回序列化数据result给Caller
776          return new MyParcelable(num + 1, `send ${receivedData.str} succeed`) as rpc.Parcelable;
777        };
778
779        export default class CalleeAbility extends UIAbility {
780          caller: Caller | undefined;
781
782          onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
783            try {
784              this.callee.on(MSG_SEND_METHOD, sendMsgCallback);
785            } catch (error) {
786              hilog.error(DOMAIN_NUMBER, TAG, '%{public}s', `Failed to register. Error is ${error}`);
787            }
788          }
789
790          //...
791          releaseCall(): void {
792            try {
793              if (this.caller) {
794                this.caller.release();
795                this.caller = undefined;
796              }
797              hilog.info(DOMAIN_NUMBER, TAG, 'caller release succeed');
798            } catch (error) {
799              hilog.error(DOMAIN_NUMBER, TAG, `caller release failed with ${error}`);
800            }
801          }
802
803          //...
804          onDestroy(): void {
805            try {
806              this.callee.off(MSG_SEND_METHOD);
807              hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', 'Callee OnDestroy');
808              this.releaseCall();
809            } catch (error) {
810              hilog.error(DOMAIN_NUMBER, TAG, '%{public}s', `Failed to register. Error is ${error}`);
811            }
812          }
813        }
814        ```
815
8164. 获取Caller接口,访问被调用端UIAbility。
817    1. 导入[UIAbility](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md)模块。
818
819        ```ts
820        import { UIAbility } from '@kit.AbilityKit';
821        ```
822    2. 获取Caller通信接口。
823        Ability的context属性实现了[startAbilityByCall](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext.md#startabilitybycall)方法,用于获取指定通用组件的[Caller](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#caller)通信接口。如下示例通过this.context获取Ability实例的context属性,使用startAbilityByCall拉起[Callee](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#callee)被调用端并获取Caller通信接口,注册Caller的[onRelease](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#onrelease)和[onRemoteStateChange](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#onremotestatechange10)监听。应用开发者根据实际业务需要做相应处理。
824
825        ```ts
826        import { BusinessError } from '@kit.BasicServicesKit';
827        import { Caller, common } from '@kit.AbilityKit';
828        import { hilog } from '@kit.PerformanceAnalysisKit';
829        import { distributedDeviceManager } from '@kit.DistributedServiceKit';
830        import { promptAction } from '@kit.ArkUI';
831
832
833        const TAG: string = '[Page_CollaborateAbility]';
834        const DOMAIN_NUMBER: number = 0xFF00;
835        let caller: Caller | undefined;
836        let dmClass: distributedDeviceManager.DeviceManager;
837
838        function getRemoteDeviceId(): string | undefined {
839          if (typeof dmClass === 'object' && dmClass !== null) {
840            let list = dmClass.getAvailableDeviceListSync();
841            hilog.info(DOMAIN_NUMBER, TAG, JSON.stringify(dmClass), JSON.stringify(list));
842            if (typeof (list) === 'undefined' || typeof (list.length) === 'undefined') {
843              hilog.error(DOMAIN_NUMBER, TAG, 'getRemoteDeviceId err: list is null');
844              return;
845            }
846            if (list.length === 0) {
847              hilog.error(DOMAIN_NUMBER, TAG, `getRemoteDeviceId err: list is empty`);
848              return;
849            }
850            return list[0].networkId;
851          } else {
852            hilog.error(DOMAIN_NUMBER, TAG, 'getRemoteDeviceId err: dmClass is null');
853            return;
854          }
855        };
856
857        @Entry
858        @Component
859        struct Page_CollaborateAbility {
860          private context = this.getUIContext().getHostContext();
861          build() {
862            Column() {
863              //...
864              List({ initialIndex: 0 }) {
865                //...
866                ListItem() {
867                  Row() {
868                    //...
869                  }
870                  .onClick(() => {
871                    let caller: Caller | undefined;
872                    let context = this.context;
873
874                    context.startAbilityByCall({
875                      deviceId: getRemoteDeviceId(),
876                      bundleName: 'com.samples.stagemodelabilityinteraction',
877                      abilityName: 'CalleeAbility'
878                    }).then((data) => {
879                      if (data !== null) {
880                        caller = data;
881                        hilog.info(DOMAIN_NUMBER, TAG, 'get remote caller success');
882                        // 注册caller的release监听
883                        caller.onRelease((msg) => {
884                          hilog.info(DOMAIN_NUMBER, TAG, `remote caller onRelease is called ${msg}`);
885                        });
886                        hilog.info(DOMAIN_NUMBER, TAG, 'remote caller register OnRelease succeed');
887                        promptAction.openToast({
888                          message: 'CallerSuccess'
889                        });
890                        // 注册caller的协同场景下跨设备组件状态变化监听通知
891                        try {
892                          caller.onRemoteStateChange((str) => {
893                            hilog.info(DOMAIN_NUMBER, TAG, 'Remote state changed ' + str);
894                          });
895                        } catch (error) {
896                          hilog.error(DOMAIN_NUMBER, TAG, `Caller.onRemoteStateChange catch error, error.code: ${JSON.stringify(error.code)}, error.message: ${JSON.stringify(error.message)}`);
897                        }
898                      }
899                    }).catch((error: BusinessError) => {
900                      hilog.error(DOMAIN_NUMBER, TAG, `get remote caller failed with ${error}`);
901                    });
902                  })
903                }
904                //...
905              }
906              //...
907            }
908            //...
909          }
910        }
911        ```
912
913        getRemoteDeviceId方法参照[通过跨设备启动uiability和serviceextensionability组件实现多端协同无返回数据](#通过跨设备启动uiability和serviceextensionability组件实现多端协同无返回数据)。
914
9155. 向被调用端UIAbility发送约定序列化数据。
916    1. 向被调用端发送Parcelable数据有两种方式,一种是不带返回值,一种是获取被调用端返回的数据,method以及序列化数据需要与被调用端协商一致。如下示例调用[Call](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#call)接口,向[Callee](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#callee)被调用端发送数据。
917
918        ```ts
919        import { UIAbility, Caller } from '@kit.AbilityKit';
920        import { rpc } from '@kit.IPCKit';
921        import { hilog } from '@kit.PerformanceAnalysisKit';
922
923        const TAG: string = '[CalleeAbility]';
924        const DOMAIN_NUMBER: number = 0xFF00;
925        const MSG_SEND_METHOD: string = 'CallSendMsg';
926
927        class MyParcelable {
928          num: number = 0;
929          str: string = '';
930
931          constructor(num: number, string: string) {
932            this.num = num;
933            this.str = string;
934          };
935
936          mySequenceable(num: number, string: string): void {
937            this.num = num;
938            this.str = string;
939          };
940
941          marshalling(messageSequence: rpc.MessageSequence): boolean {
942            messageSequence.writeInt(this.num);
943            messageSequence.writeString(this.str);
944            return true;
945          };
946
947          unmarshalling(messageSequence: rpc.MessageSequence): boolean {
948            this.num = messageSequence.readInt();
949            this.str = messageSequence.readString();
950            return true;
951          };
952        }
953
954        export default class EntryAbility extends UIAbility {
955          // ...
956          caller: Caller | undefined;
957
958          async onButtonCall(): Promise<void> {
959            try {
960              let msg: MyParcelable = new MyParcelable(1, 'origin_Msg');
961              if (this.caller) {
962                await this.caller.call(MSG_SEND_METHOD, msg);
963              }
964            } catch (error) {
965              hilog.error(DOMAIN_NUMBER, TAG, `caller call failed with ${error}`);
966            }
967          }
968          // ...
969        }
970        ```
971    2. 如下示例调用[CallWithResult](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#callwithresult)接口,向Callee被调用端发送待处理的数据originMsg,并将’CallSendMsg’方法处理完毕的数据赋值给backMsg。
972
973        ```ts
974        import { UIAbility, Caller } from '@kit.AbilityKit';
975        import { rpc } from '@kit.IPCKit';
976        import { hilog } from '@kit.PerformanceAnalysisKit';
977
978        const TAG: string = '[CalleeAbility]';
979        const DOMAIN_NUMBER: number = 0xFF00;
980
981        const MSG_SEND_METHOD: string = 'CallSendMsg';
982        let originMsg: string = '';
983        let backMsg: string = '';
984
985        class MyParcelable {
986          num: number = 0;
987          str: string = '';
988
989          constructor(num: number, string: string) {
990            this.num = num;
991            this.str = string;
992          };
993
994          mySequenceable(num: number, string: string): void {
995            this.num = num;
996            this.str = string;
997          };
998
999          marshalling(messageSequence: rpc.MessageSequence): boolean {
1000            messageSequence.writeInt(this.num);
1001            messageSequence.writeString(this.str);
1002            return true;
1003          };
1004
1005          unmarshalling(messageSequence: rpc.MessageSequence): boolean {
1006            this.num = messageSequence.readInt();
1007            this.str = messageSequence.readString();
1008            return true;
1009          };
1010        }
1011
1012        export default class EntryAbility extends UIAbility {
1013          // ...
1014          caller: Caller | undefined;
1015
1016          async onButtonCallWithResult(originMsg: string, backMsg: string): Promise<void> {
1017            try {
1018              let msg: MyParcelable = new MyParcelable(1, originMsg);
1019              if (this.caller) {
1020                const data = await this.caller.callWithResult(MSG_SEND_METHOD, msg);
1021                hilog.info(DOMAIN_NUMBER, TAG, 'caller callWithResult succeed');
1022                let result: MyParcelable = new MyParcelable(0, '');
1023                data.readParcelable(result);
1024                backMsg = result.str;
1025                hilog.info(DOMAIN_NUMBER, TAG, `caller result is [${result.num}, ${result.str}]`);
1026              }
1027            } catch (error) {
1028              hilog.error(DOMAIN_NUMBER, TAG, `caller callWithResult failed with ${error}`);
1029            }
1030          }
1031          // ...
1032        }
1033        ```
1034
10356. 释放Caller通信接口。
1036
1037    [Caller](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#caller)不再使用后,应用开发者可以通过[release](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#release)接口释放Caller。
1038
1039      ```ts
1040      import { UIAbility, Caller } from '@kit.AbilityKit';
1041      import { hilog } from '@kit.PerformanceAnalysisKit';
1042
1043      const TAG: string = '[CalleeAbility]';
1044      const DOMAIN_NUMBER: number = 0xFF00;
1045
1046      export default class EntryAbility extends UIAbility {
1047        caller: Caller | undefined;
1048
1049        releaseCall(): void {
1050          try {
1051            if (this.caller) {
1052              this.caller.release();
1053              this.caller = undefined;
1054            }
1055            hilog.info(DOMAIN_NUMBER, TAG, 'caller release succeed');
1056          } catch (error) {
1057            hilog.error(DOMAIN_NUMBER, TAG, `caller release failed with ${error}`);
1058          }
1059        }
1060      }
1061      ```
1062<!--no_check-->