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