• 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。
37
38
39### 接口说明
40
41  **表1** 跨设备启动API接口功能介绍
42
43| **接口名** | **描述** |
44| -------- | -------- |
45| startAbility(want: Want, callback: AsyncCallback<void>): void; | 启动UIAbility和ServiceExtensionAbility(callback形式)。 |
46
47
48### 开发步骤
49
501. 申请`ohos.permission.DISTRIBUTED_DATASYNC`权限,配置方式请参阅[访问控制授权申请指导](../security/accesstoken-guidelines.md#stage模型)。
51
522. 申请数据同步权限,弹框示例代码。
53
54   ```ts
55   requestPermission() {
56       let context = this.context
57       let permissions: Array<string> = ['ohos.permission.DISTRIBUTED_DATASYNC']
58       context.requestPermissionsFromUser(permissions).then((data) => {
59           console.info("Succeed to request permission from user with data: "+ JSON.stringify(data))
60       }).catch((error) => {
61           console.info("Failed to request permission from user with error: "+ JSON.stringify(error))
62       })
63   }
64   ```
65
663. 获取目标设备的设备ID。
67
68   ```ts
69   import deviceManager from '@ohos.distributedHardware.deviceManager';
70
71   let dmClass;
72   function initDmClass() {
73       // 其中createDeviceManager接口为系统API
74       deviceManager.createDeviceManager('ohos.samples.demo', (err, dm) => {
75           if (err) {
76               // ...
77               return
78           }
79           dmClass = dm
80       })
81   }
82   function getRemoteDeviceId() {
83       if (typeof dmClass === 'object' && dmClass !== null) {
84           let list = dmClass.getTrustedDeviceListSync()
85           if (typeof (list) === 'undefined' || typeof (list.length) === 'undefined') {
86               console.info('EntryAbility onButtonClick getRemoteDeviceId err: list is null')
87               return;
88           }
89           return list[0].deviceId
90       } else {
91           console.info('EntryAbility onButtonClick getRemoteDeviceId err: dmClass is null')
92       }
93   }
94   ```
95
964. 设置目标组件参数,调用startAbility()接口,启动UIAbility或ServiceExtensionAbility。
97
98   ```ts
99   let want = {
100       deviceId: getRemoteDeviceId(),
101       bundleName: 'com.example.myapplication',
102       abilityName: 'FuncAbility',
103       moduleName: 'module1', // moduleName非必选
104   }
105   // context为发起端UIAbility的AbilityContext
106   this.context.startAbility(want).then(() => {
107       // ...
108   }).catch((err) => {
109       // ...
110   })
111   ```
112
113
114## 通过跨设备启动UIAbility组件实现多端协同(获取返回数据)
115
116在设备A上通过应用提供的启动按钮,启动设备B上指定的UIAbility,当设备B上的UIAbility退出后,会将返回值发回设备A上的发起端应用。
117
118
119### 接口说明
120
121  **表2** 跨设备启动,返回结果数据API接口功能描述
122
123| 接口名 | 描述 |
124| -------- | -------- |
125| startAbilityForResult(want:&nbsp;Want,&nbsp;callback:&nbsp;AsyncCallback&lt;AbilityResult&gt;):&nbsp;void; | 启动UIAbility并在该Ability退出的时候返回执行结果(callback形式)。 |
126| terminateSelfWithResult(parameter:&nbsp;AbilityResult,&nbsp;callback:&nbsp;AsyncCallback&lt;void&gt;):&nbsp;void; | 停止UIAbility,配合startAbilityForResult使用,返回给接口调用方AbilityResult信息(callback形式)。 |
127| terminateSelfWithResult(parameter:&nbsp;AbilityResult):&nbsp;Promise&lt;void&gt;; | 停止UIAbility,配合startAbilityForResult使用,返回给接口调用方AbilityResult信息(promise形式)。 |
128
129
130### 开发步骤
131
1321. 申请`ohos.permission.DISTRIBUTED_DATASYNC`权限,配置方式请参阅[访问控制授权申请指导](../security/accesstoken-guidelines.md#stage模型)。
133
1342. 申请数据同步权限,弹框示例代码。
135
136   ```ts
137   requestPermission() {
138       let context = this.context
139       let permissions: Array<string> = ['ohos.permission.DISTRIBUTED_DATASYNC']
140       context.requestPermissionsFromUser(permissions).then((data) => {
141           console.info("Succeed to request permission from user with data: "+ JSON.stringify(data))
142       }).catch((error) => {
143           console.info("Failed to request permission from user with error: "+ JSON.stringify(error))
144       })
145   }
146   ```
147
1483. 在发起端设置目标组件参数,调用startAbilityForResult()接口启动目标端UIAbility,异步回调中的data用于接收目标端UIAbility停止自身后返回给调用方UIAbility的信息。getRemoteDeviceId方法参照[通过跨设备启动uiability和serviceextensionability组件实现多端协同无返回数据](#通过跨设备启动uiability和serviceextensionability组件实现多端协同无返回数据)。
149
150   ```ts
151   let want = {
152       deviceId: getRemoteDeviceId(),
153       bundleName: 'com.example.myapplication',
154       abilityName: 'FuncAbility',
155       moduleName: 'module1', // moduleName非必选
156   }
157   // context为发起端UIAbility的AbilityContext
158   this.context.startAbilityForResult(want).then((data) => {
159       // ...
160   }).catch((err) => {
161       // ...
162   })
163   ```
164
1654. 在目标端UIAbility任务完成后,调用terminateSelfWithResult()方法,将数据返回给发起端的UIAbility。
166
167   ```ts
168   const RESULT_CODE: number = 1001;
169   let abilityResult = {
170       resultCode: RESULT_CODE,
171       want: {
172           bundleName: 'com.example.myapplication',
173           abilityName: 'FuncAbility',
174           moduleName: 'module1',
175       },
176   }
177   // context为目标端UIAbility的AbilityContext
178   this.context.terminateSelfWithResult(abilityResult, (err) => {
179       // ...
180   });
181   ```
182
1835. 发起端UIAbility接收到目标端UIAbility返回的信息,对其进行处理。
184
185   ```ts
186   const RESULT_CODE: number = 1001;
187
188   // ...
189
190   // context为调用方UIAbility的AbilityContext
191   this.context.startAbilityForResult(want).then((data) => {
192       if (data?.resultCode === RESULT_CODE) {
193           // 解析目标端UIAbility返回的信息
194           let info = data.want?.parameters?.info
195           // ...
196       }
197   }).catch((err) => {
198       // ...
199   })
200   ```
201
202
203## 通过跨设备连接ServiceExtensionAbility组件实现多端协同
204
205系统应用可以通过[connectServiceExtensionAbility()](../reference/apis/js-apis-inner-application-uiAbilityContext.md#abilitycontextconnectserviceextensionability)跨设备连接一个服务,实现跨设备远程调用。比如:分布式游戏场景,平板作为遥控器,智慧屏作为显示器。
206
207
208### 接口说明
209
210  **表3** 跨设备连接API接口功能介绍
211
212| 接口名 | 描述 |
213| -------- | -------- |
214| connectServiceExtensionAbility(want:&nbsp;Want,&nbsp;options:&nbsp;ConnectOptions):&nbsp;number; | 连接ServiceExtensionAbility。 |
215| disconnectServiceExtensionAbility(connection:&nbsp;number,&nbsp;callback:AsyncCallback&lt;void&gt;):&nbsp;void; | 断开连接(callback形式)。 |
216| disconnectServiceExtensionAbility(connection:&nbsp;number):&nbsp;Promise&lt;void&gt;; | 断开连接(promise形式)。 |
217
218
219### 开发步骤
220
2211. 在module.json5配置数据同步权限,示例代码如下。
222
223   ```json
224   {
225     "module": {
226       "requestPermissions":[
227         {
228           "name" : "ohos.permission.DISTRIBUTED_DATASYNC",
229         }
230       ]
231     }
232   }
233   ```
234
2352. 申请数据同步权限,弹框示例代码。
236
237   ```ts
238   requestPermission() {
239       let context = this.context
240       let permissions: Array<string> = ['ohos.permission.DISTRIBUTED_DATASYNC']
241       context.requestPermissionsFromUser(permissions).then((data) => {
242           console.info("Succeed to request permission from user with data: "+ JSON.stringify(data))
243       }).catch((error) => {
244           console.info("Failed to request permission from user with error: "+ JSON.stringify(error))
245       })
246   }
247   ```
248
2493. 如果已有后台服务,请直接进入下一步;如果没有,则[实现一个后台服务](serviceextensionability.md#实现一个后台服务仅对系统应用开放)。
250
2514. 连接一个后台服务。
252   - 实现IAbilityConnection接口。IAbilityConnection提供了以下方法供开发者实现:onConnect()是用来处理连接Service成功的回调,onDisconnect()是用来处理Service异常终止的回调,onFailed()是用来处理连接Service失败的回调。
253   - 设置目标组件参数,包括目标设备ID、包名、ability名。
254   - 调用connectServiceExtensionAbility发起连接。
255   - 连接成功,收到目标设备返回的服务句柄。
256   - 进行跨设备调用,获得目标端服务返回的结果。
257
258      ```ts
259      import rpc from '@ohos.rpc';
260
261      const REQUEST_CODE = 99;
262      let want = {
263          "deviceId": getRemoteDeviceId(),
264          "bundleName": "com.example.myapplication",
265          "abilityName": "ServiceExtAbility"
266      };
267      let options = {
268          onConnect(elementName, remote) {
269              console.info('onConnect callback');
270              if (remote === null) {
271                  console.info(`onConnect remote is null`);
272                  return;
273              }
274              let option = new rpc.MessageOption();
275              let data = new rpc.MessageParcel();
276              let reply = new rpc.MessageParcel();
277              data.writeInt(1);
278              data.writeInt(99);  // 开发者可发送data到目标端应用进行相应操作
279
280              // @param code 表示客户端发送的服务请求代码。
281              // @param data 表示客户端发送的{@link MessageParcel}对象。
282              // @param reply 表示远程服务发送的响应消息对象。
283              // @param options 指示操作是同步的还是异步的。
284              //
285              // @return 如果操作成功返回{@code true}; 否则返回 {@code false}。
286              remote.sendRequest(REQUEST_CODE, data, reply, option).then((ret) => {
287                  let msg = reply.readInt();   // 在成功连接的情况下,会收到来自目标端返回的信息(100)
288                  console.info(`sendRequest ret:${ret} msg:${msg}`);
289              }).catch((error) => {
290                  console.info('sendRequest failed');
291              });
292          },
293          onDisconnect(elementName) {
294              console.info('onDisconnect callback')
295          },
296          onFailed(code) {
297              console.info('onFailed callback')
298          }
299      }
300      // 建立连接后返回的Id需要保存下来,在解绑服务时需要作为参数传入
301      let connectionId = this.context.connectServiceExtensionAbility(want, options);
302      ```
303
304      getRemoteDeviceId方法参照[通过跨设备启动uiability和serviceextensionability组件实现多端协同无返回数据](#通过跨设备启动uiability和serviceextensionability组件实现多端协同无返回数据)。
305
3065. 断开连接。调用disconnectServiceExtensionAbility()断开与后台服务的连接。
307
308   ```ts
309   let connectionId = 1 // 在通过connectServiceExtensionAbility绑定服务时返回的Id
310   this.context.disconnectServiceExtensionAbility(connectionId).then((data) => {
311       console.info('disconnectServiceExtensionAbility success');
312   }).catch((error) => {
313       console.error('disconnectServiceExtensionAbility failed');
314   })
315   ```
316
317
318## 通过跨设备Call调用实现多端协同
319
320跨设备Call调用的基本原理与设备内Call调用相同,请参见[通过Call调用实现UIAbility交互(仅对系统应用开放)](uiability-intra-device-interaction.md#通过call调用实现uiability交互仅对系统应用开放)。
321
322下面介绍跨设备Call调用实现多端协同的方法。
323
324
325### 接口说明
326
327  **表4** Call API接口功能介绍
328
329| 接口名 | 描述 |
330| -------- | -------- |
331| startAbilityByCall(want:&nbsp;Want):&nbsp;Promise&lt;Caller&gt;; | 启动指定UIAbility至前台或后台,同时获取其Caller通信接口,调用方可使用Caller与被启动的Ability进行通信。 |
332| on(method:&nbsp;string,&nbsp;callback:&nbsp;CalleeCallBack):&nbsp;void | 通用组件Callee注册method对应的callback方法。 |
333| off(method:&nbsp;string):&nbsp;void | 通用组件Callee解注册method的callback方法。 |
334| call(method:&nbsp;string,&nbsp;data:&nbsp;rpc.Parcelable):&nbsp;Promise&lt;void&gt; | 向通用组件Callee发送约定序列化数据。 |
335| callWithResult(method:&nbsp;string,&nbsp;data:&nbsp;rpc.Parcelable):&nbsp;Promise&lt;rpc.MessageSequence&gt; | 向通用组件Callee发送约定序列化数据,&nbsp;并将Callee返回的约定序列化数据带回。 |
336| release():&nbsp;void | 释放通用组件的Caller通信接口。 |
337| on(type:&nbsp;"release",&nbsp;callback:&nbsp;OnReleaseCallback):&nbsp;void | 注册通用组件通信断开监听通知。 |
338
339
340### 开发步骤
341
3421. 在module.json5配置数据同步权限,示例代码如下。
343
344   ```json
345   {
346     "module": {
347       "requestPermissions":[
348         {
349           "name" : "ohos.permission.DISTRIBUTED_DATASYNC",
350         }
351       ]
352     }
353   }
354   ```
355
3562. 申请数据同步权限,弹框示例代码。
357
358   ```ts
359   requestPermission() {
360       let context = this.context
361       let permissions: Array<string> = ['ohos.permission.DISTRIBUTED_DATASYNC']
362       context.requestPermissionsFromUser(permissions).then((data) => {
363           console.info("Succeed to request permission from user with data: "+ JSON.stringify(data))
364       }).catch((error) => {
365           console.info("Failed to request permission from user with error: "+ JSON.stringify(error))
366       })
367   }
368   ```
369
3703. 创建被调用端UIAbility。
371   被调用端UIAbility需要实现指定方法的数据接收回调函数、数据的序列化及反序列化方法。在需要接收数据期间,通过on接口注册监听,无需接收数据时通过off接口解除监听。
372
373   1. 配置UIAbility的启动模式。
374       配置module.json5,将CalleeAbility配置为单实例"singleton"。
375
376       | Json字段 | 字段说明 |
377       | -------- | -------- |
378       | “launchType” | Ability的启动模式,设置为"singleton"类型。 |
379
380       UIAbility配置标签示例如下:
381
382
383       ```json
384       "abilities":[{
385           "name": ".CalleeAbility",
386           "srcEnty": "./ets/CalleeAbility/CalleeAbility.ts",
387           "launchType": "singleton",
388           "description": "$string:CalleeAbility_desc",
389           "icon": "$media:icon",
390           "label": "$string:CalleeAbility_label",
391           "exported": true
392       }]
393       ```
394   2. 导入UIAbility模块。
395
396       ```ts
397       import Ability from '@ohos.app.ability.UIAbility'
398       ```
399   3. 定义约定的序列化数据。
400       调用端及被调用端发送接收的数据格式需协商一致,如下示例约定数据由number和string组成。
401
402
403       ```ts
404       export default class MyParcelable {
405           num: number = 0
406           str: string = ""
407
408           constructor(num, string) {
409               this.num = num
410               this.str = string
411           }
412
413           marshalling(messageSequence) {
414               messageSequence.writeInt(this.num)
415               messageSequence.writeString(this.str)
416               return true
417           }
418
419           unmarshalling(messageSequence) {
420               this.num = messageSequence.readInt()
421               this.str = messageSequence.readString()
422               return true
423           }
424       }
425       ```
426   4. 实现Callee.on监听及Callee.off解除监听。
427         如下示例在Ability的onCreate注册MSG_SEND_METHOD监听,在onDestroy取消监听,收到序列化数据后作相应处理并返回。应用开发者根据实际业务需要做相应处理。
428
429       ```ts
430       const TAG: string = '[CalleeAbility]'
431       const MSG_SEND_METHOD: string = 'CallSendMsg'
432
433       function sendMsgCallback(data) {
434           console.info('CalleeSortFunc called')
435
436           // 获取Caller发送的序列化数据
437           let receivedData = new MyParcelable(0, '')
438           data.readParcelable(receivedData)
439           console.info(`receiveData[${receivedData.num}, ${receivedData.str}]`)
440
441           // 作相应处理
442           // 返回序列化数据result给Caller
443           return new MyParcelable(receivedData.num + 1, `send ${receivedData.str} succeed`)
444       }
445
446       export default class CalleeAbility extends Ability {
447           onCreate(want, launchParam) {
448               try {
449                   this.callee.on(MSG_SEND_METHOD, sendMsgCallback)
450               } catch (error) {
451                   console.info(`${MSG_SEND_METHOD} register failed with error ${JSON.stringify(error)}`)
452               }
453           }
454
455           onDestroy() {
456               try {
457                   this.callee.off(MSG_SEND_METHOD)
458               } catch (error) {
459                   console.error(TAG, `${MSG_SEND_METHOD} unregister failed with error ${JSON.stringify(error)}`)
460               }
461           }
462       }
463       ```
464
4654. 获取Caller接口,访问被调用端UIAbility。
466   1. 导入UIAbility模块。
467
468       ```ts
469       import Ability from '@ohos.app.ability.UIAbility'
470       ```
471   2. 获取Caller通信接口。
472       Ability的context属性实现了startAbilityByCall方法,用于获取指定通用组件的Caller通信接口。如下示例通过this.context获取Ability实例的context属性,使用startAbilityByCall拉起Callee被调用端并获取Caller通信接口,注册Caller的onRelease监听。应用开发者根据实际业务需要做相应处理。
473
474
475       ```ts
476       async onButtonGetRemoteCaller() {
477           var caller = undefined
478           var context = this.context
479
480           context.startAbilityByCall({
481               deviceId: getRemoteDeviceId(),
482               bundleName: 'com.samples.CallApplication',
483               abilityName: 'CalleeAbility'
484           }).then((data) => {
485               if (data != null) {
486                   caller = data
487                   console.info('get remote caller success')
488                   // 注册caller的release监听
489                   caller.onRelease((msg) => {
490                       console.info(`remote caller onRelease is called ${msg}`)
491                   })
492                   console.info('remote caller register OnRelease succeed')
493               }
494           }).catch((error) => {
495               console.error(`get remote caller failed with ${error}`)
496           })
497       }
498       ```
499
500       getRemoteDeviceId方法参照[通过跨设备启动uiability和serviceextensionability组件实现多端协同无返回数据](#通过跨设备启动uiability和serviceextensionability组件实现多端协同无返回数据)。
501
5025. 向被调用端UIAbility发送约定序列化数据。
503   1. 向被调用端发送Parcelable数据有两种方式,一种是不带返回值,一种是获取被调用端返回的数据,method以及序列化数据需要与被调用端协商一致。如下示例调用Call接口,向Callee被调用端发送数据。
504
505       ```ts
506       const MSG_SEND_METHOD: string = 'CallSendMsg'
507       async onButtonCall() {
508           try {
509               let msg = new MyParcelable(1, 'origin_Msg')
510               await this.caller.call(MSG_SEND_METHOD, msg)
511           } catch (error) {
512               console.info(`caller call failed with ${error}`)
513           }
514       }
515       ```
516   2. 如下示例调用CallWithResult接口,向Callee被调用端发送待处理的数据originMsg,并将’CallSendMsg’方法处理完毕的数据赋值给backMsg。
517
518       ```ts
519       const MSG_SEND_METHOD: string = 'CallSendMsg'
520       originMsg: string = ''
521       backMsg: string = ''
522       async onButtonCallWithResult(originMsg, backMsg) {
523           try {
524               let msg = new MyParcelable(1, originMsg)
525               const data = await this.caller.callWithResult(MSG_SEND_METHOD, msg)
526               console.info('caller callWithResult succeed')
527
528               let result = new MyParcelable(0, '')
529               data.readParcelable(result)
530               backMsg(result.str)
531               console.info(`caller result is [${result.num}, ${result.str}]`)
532           } catch (error) {
533               console.info(`caller callWithResult failed with ${error}`)
534           }
535       }
536       ```
537
5386. 释放Caller通信接口。
539     Caller不再使用后,应用开发者可以通过release接口释放Caller。
540
541   ```ts
542   releaseCall() {
543       try {
544           this.caller.release()
545           this.caller = undefined
546           console.info('caller release succeed')
547       } catch (error) {
548           console.info(`caller release failed with ${error}`)
549       }
550   }
551   ```
552