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: Want, callback: AsyncCallback<AbilityResult>): void; | 启动UIAbility并在该Ability退出的时候返回执行结果(callback形式)。 | 126| terminateSelfWithResult(parameter: AbilityResult, callback: AsyncCallback<void>): void; | 停止UIAbility,配合startAbilityForResult使用,返回给接口调用方AbilityResult信息(callback形式)。 | 127| terminateSelfWithResult(parameter: AbilityResult): Promise<void>; | 停止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: Want, options: ConnectOptions): number; | 连接ServiceExtensionAbility。 | 215| disconnectServiceExtensionAbility(connection: number, callback:AsyncCallback<void>): void; | 断开连接(callback形式)。 | 216| disconnectServiceExtensionAbility(connection: number): Promise<void>; | 断开连接(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: Want): Promise<Caller>; | 启动指定UIAbility至前台或后台,同时获取其Caller通信接口,调用方可使用Caller与被启动的Ability进行通信。 | 332| on(method: string, callback: CalleeCallBack): void | 通用组件Callee注册method对应的callback方法。 | 333| off(method: string): void | 通用组件Callee解注册method的callback方法。 | 334| call(method: string, data: rpc.Parcelable): Promise<void> | 向通用组件Callee发送约定序列化数据。 | 335| callWithResult(method: string, data: rpc.Parcelable): Promise<rpc.MessageSequence> | 向通用组件Callee发送约定序列化数据, 并将Callee返回的约定序列化数据带回。 | 336| release(): void | 释放通用组件的Caller通信接口。 | 337| on(type: "release", callback: OnReleaseCallback): 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