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