1# 多端协同 2 3 4## 功能描述 5 6多端协同主要包括如下场景: 7 8- [通过跨设备启动UIAbility和ServiceExtensionAbility组件实现多端协同(无返回数据)](#通过跨设备启动uiability和serviceextensionability组件实现多端协同无返回数据) 9 10- [通过跨设备启动UIAbility组件实现多端协同(获取返回数据)](#通过跨设备启动uiability组件实现多端协同获取返回数据) 11 12- [通过跨设备连接ServiceExtensionAbility组件实现多端协同](#通过跨设备连接serviceextensionability组件实现多端协同) 13 14- [通过跨设备Call调用实现多端协同](#通过跨设备call调用实现多端协同) 15 16 17## 多端协同流程 18 19多端协同流程如下图所示。 20 21 **图1** 多端协同流程图 22 23 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-->