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