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