文档管理中心
您当前正在浏览新版开发者文档中心,目录分类和层级有所调整。点击左侧当前文档分类名称前的“☰”图标,可切换文档分类。 了解新版目录
指南与API参考指南应用框架Ability Kit(程序框架服务)应用模型应用组件UIAbility组件通过Call调用实现多端协同

通过Call调用实现多端协同

Call调用是UIAbility能力的扩展,它为UIAbility提供一种能够被外部调用并与外部进行通信的能力。Call调用支持前台与后台两种启动方式,使UIAbility既能被拉起到前台展示UI,也可以在后台被创建并运行。通过建立跨进程通信(IPC)链路,它在调用方与被调用方间构建起数据通道。当在分布式场景下使用时,Call调用可以跨设备发起,使得一个设备上的应用能够将任务迁移至另一个设备上的UIAbility继续执行,从而完成跨端迁移。

Call调用的核心接口是startAbilityByCall()方法,与startAbility()接口的不同之处在于:

  • startAbilityByCall支持前台与后台两种启动方式,而startAbility()仅支持前台启动。

  • 调用方可使用startAbilityByCall()所返回的Caller对象与被调用方进行通信,而startAbility()不具备通信能力。

基本概念

表1 Call调用相关名词解释

展开
名词 描述
CallerAbility 进行Call调用的UIAbility(调用方)。
CalleeAbility 被Call调用的UIAbility(被调用方)。
Caller 实际对象,由startAbilityByCall接口返回,CallerAbility可使用Caller与CalleeAbility进行通信。
Callee 实际对象,被CalleeAbility持有,可与Caller进行通信。

约束限制

  • CalleeAbility的启动模式不支持指定实例模式。

  • 当前仅分布式迁移场景对第三方应用开放Call调用权限,其余所有Call调用场景均限定为系统内部调用。

运行机制

Call调用示意图如下所示。

图1 Call调用示意图

  • CallerAbility调用startAbilityByCall()接口获取Caller,并使用Caller对象的call()方法向CalleeAbility发送数据。

  • CalleeAbility持有一个Callee对象,通过Callee的on()方法注册回调函数,当接收到Caller发送的数据时将会调用对应的回调函数。

接口说明

Call功能主要接口如下表所示。具体的API详见Caller接口说明。

表2 Call功能主要接口

展开
接口名 描述
startAbilityByCall(want: Want): Promise<Caller> 启动指定UIAbility并获取其Caller通信接口,默认为后台启动,通过配置want可实现前台启动,详见startAbilityByCall()接口说明。AbilityContext与ServiceExtensionContext均支持该接口。
on(method: string, callback: CalleeCallBack): void 通用组件Callee注册method对应的callback方法。
off(method: string): void 通用组件Callee解注册method的callback方法。
call(method: string, data: rpc.Parcelable): Promise<void> 向通用组件Callee发送约定序列化数据。
callWithResult(method: string, data: rpc.Parcelable): Promise<rpc.MessageSequence> 向通用组件Callee发送约定序列化数据,并将Callee返回的约定序列化数据带回。
release(): void 释放通用组件的Caller通信接口。
on(type: "release", callback: OnReleaseCallback): void 注册通用组件通信断开监听通知。

开发步骤

创建Callee被调用端

在Callee被调用端,需要实现指定方法的数据接收回调函数、数据的序列化及反序列化方法。在需要接收数据期间,通过on()接口注册监听,无需接收数据时通过off()接口解除监听。

  1. 需要申请ohos.permission.DISTRIBUTED_DATASYNC权限,配置方式请参见声明权限。

  2. 同时需要在应用首次启动时弹窗向用户申请授权,使用方式请参见向用户申请授权。

  3. 配置UIAbility的启动模式。

    例如将CalleeAbility配置为单实例模式singleton,配置方式请参见UIAbility组件启动模式。

  4. 定义约定的序列化数据。

    调用端及被调用端发送接收的数据格式需协商一致,如下示例约定数据由number和string组成。

    收起
    自动换行
    深色代码主题
    复制
    1. import { rpc } from '@kit.IPCKit';
    2. class MyParcelable {
    3. num: number = 0;
    4. str: string = '';
    5. constructor(num: number, string: string) {
    6. this.num = num;
    7. this.str = string;
    8. }
    9. mySequenceable(num: number, string: string): void {
    10. this.num = num;
    11. this.str = string;
    12. }
    13. marshalling(messageSequence: rpc.MessageSequence): boolean {
    14. messageSequence.writeInt(this.num);
    15. messageSequence.writeString(this.str);
    16. return true;
    17. }
    18. unmarshalling(messageSequence: rpc.MessageSequence): boolean {
    19. this.num = messageSequence.readInt();
    20. this.str = messageSequence.readString();
    21. return true;
    22. }
    23. }
  5. 实现Callee.on监听及Callee.off解除监听。

    被调用端Callee的监听函数注册时机,取决于应用开发者。注册监听之前的数据不会被处理,取消监听之后的数据不会被处理。如下示例在UIAbility的onCreate()注册'MSG_SEND_METHOD'监听,在onDestroy()取消监听,收到序列化数据后作相应处理并返回,应用开发者根据实际需要做相应处理。具体示例代码如下:

    收起
    自动换行
    深色代码主题
    复制
    1. import { AbilityConstant, UIAbility, Want, Caller } from '@kit.AbilityKit';
    2. import { hilog } from '@kit.PerformanceAnalysisKit';
    3. import { rpc } from '@kit.IPCKit';
    4. const TAG: string = '[CalleeAbility]';
    5. const MSG_SEND_METHOD: string = 'CallSendMsg';
    6. const DOMAIN_NUMBER: number = 0xFF00;
    7. class MyParcelable {
    8. num: number = 0;
    9. str: string = '';
    10. constructor(num: number, string: string) {
    11. this.num = num;
    12. this.str = string;
    13. }
    14. mySequenceable(num: number, string: string): void {
    15. this.num = num;
    16. this.str = string;
    17. }
    18. marshalling(messageSequence: rpc.MessageSequence): boolean {
    19. messageSequence.writeInt(this.num);
    20. messageSequence.writeString(this.str);
    21. return true;
    22. }
    23. unmarshalling(messageSequence: rpc.MessageSequence): boolean {
    24. this.num = messageSequence.readInt();
    25. this.str = messageSequence.readString();
    26. return true;
    27. }
    28. }
    29. function sendMsgCallback(data: rpc.MessageSequence): rpc.Parcelable {
    30. hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', 'sendMsgCallback called');
    31. // 获取Caller发送的序列化数据
    32. let receivedData: MyParcelable = new MyParcelable(0, '');
    33. data.readParcelable(receivedData);
    34. hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', `receiveData[${receivedData.num}, ${receivedData.str}]`);
    35. let num: number = receivedData.num;
    36. // 作相应处理
    37. // 返回序列化数据result给Caller
    38. return new MyParcelable(num + 1, `send ${receivedData.str} succeed`) as rpc.Parcelable;
    39. }
    40. export default class CalleeAbility extends UIAbility {
    41. caller: Caller | undefined;
    42. onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    43. try {
    44. this.callee.on(MSG_SEND_METHOD, sendMsgCallback);
    45. } catch (error) {
    46. hilog.error(DOMAIN_NUMBER, TAG, '%{public}s', `Failed to register. Error is ${error}`);
    47. }
    48. }
    49. // ...
    50. releaseCall(): void {
    51. try {
    52. if (this.caller) {
    53. this.caller.release();
    54. this.caller = undefined;
    55. }
    56. hilog.info(DOMAIN_NUMBER, TAG, 'caller release succeed');
    57. } catch (error) {
    58. hilog.error(DOMAIN_NUMBER, TAG, `caller release failed with ${error}`);
    59. }
    60. }
    61. // ...
    62. onDestroy(): void {
    63. try {
    64. this.callee.off(MSG_SEND_METHOD);
    65. hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', 'Callee OnDestroy');
    66. this.releaseCall();
    67. } catch (error) {
    68. hilog.error(DOMAIN_NUMBER, TAG, '%{public}s', `Failed to register. Error is ${error}`);
    69. }
    70. }
    71. }

访问被调用端UIAbility

  1. 导入UIAbility模块。

    收起
    自动换行
    深色代码主题
    复制
    1. import { UIAbility } from '@kit.AbilityKit';
  2. 获取Caller通信接口。

    Ability的context属性实现了startAbilityByCall()方法,用于获取指定通用组件Caller通信接口。如下示例通过this.context获取Ability实例的context属性,使用startAbilityByCall拉起Callee被调用端并获取Caller通信接口,注册Caller的onRelease()和onRemoteStateChange()监听。应用开发者根据实际业务需要做相应处理。

    收起
    自动换行
    深色代码主题
    复制
    1. import { BusinessError } from '@kit.BasicServicesKit';
    2. import { Caller, common } from '@kit.AbilityKit';
    3. import { hilog } from '@kit.PerformanceAnalysisKit';
    4. import { distributedDeviceManager } from '@kit.DistributedServiceKit';
    5. import { promptAction } from '@kit.ArkUI';
    6. const TAG: string = '[Page_CollaborateAbility]';
    7. const DOMAIN_NUMBER: number = 0xFF00;
    8. let caller: Caller | undefined;
    9. let dmClass: distributedDeviceManager.DeviceManager;
    10. function getRemoteDeviceId(): string | undefined {
    11. if (typeof dmClass === 'object' && dmClass !== null) {
    12. let list = dmClass.getAvailableDeviceListSync();
    13. hilog.info(DOMAIN_NUMBER, TAG, JSON.stringify(dmClass), JSON.stringify(list));
    14. if (typeof (list) === 'undefined' || typeof (list.length) === 'undefined') {
    15. hilog.error(DOMAIN_NUMBER, TAG, 'getRemoteDeviceId err: list is null');
    16. return;
    17. }
    18. if (list.length === 0) {
    19. hilog.error(DOMAIN_NUMBER, TAG, `getRemoteDeviceId err: list is empty`);
    20. return;
    21. }
    22. return list[0].networkId;
    23. } else {
    24. hilog.error(DOMAIN_NUMBER, TAG, 'getRemoteDeviceId err: dmClass is null');
    25. return;
    26. }
    27. }
    28. @Entry
    29. @Component
    30. struct Page_CollaborateAbility {
    31. private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
    32. build() {
    33. Row() {
    34. Column() {
    35. // ...
    36. List({ initialIndex: 0 }) {
    37. // ...
    38. ListItem() {
    39. Button('test').onClick(() => {
    40. let caller: Caller | undefined;
    41. let context = this.context;
    42. context.startAbilityByCall({
    43. deviceId: getRemoteDeviceId(),
    44. bundleName: 'com.samples.stagemodelabilityinteraction',
    45. abilityName: 'CalleeAbility'
    46. }).then((data) => {
    47. if (data !== null) {
    48. caller = data;
    49. hilog.info(DOMAIN_NUMBER, TAG, 'get remote caller success');
    50. // 注册caller的release监听
    51. caller.onRelease((msg) => {
    52. hilog.info(DOMAIN_NUMBER, TAG, `remote caller onRelease is called ${msg}`);
    53. });
    54. hilog.info(DOMAIN_NUMBER, TAG, 'remote caller register OnRelease succeed');
    55. promptAction.openToast({
    56. message: 'CallerSuccess'
    57. });
    58. // 注册caller的协同场景下跨设备组件状态变化监听通知
    59. try {
    60. caller.onRemoteStateChange((str) => {
    61. hilog.info(DOMAIN_NUMBER, TAG, 'Remote state changed ' + str);
    62. });
    63. } catch (error) {
    64. hilog.error(DOMAIN_NUMBER, TAG, `Caller.onRemoteStateChange catch error, error.code: ${JSON.stringify(error.code)}, error.message: ${JSON.stringify(error.message)}`);
    65. }
    66. }
    67. }).catch((error: BusinessError) => {
    68. hilog.error(DOMAIN_NUMBER, TAG, `get remote caller failed with ${error}`);
    69. });
    70. })
    71. }
    72. // ...
    73. }
    74. // ...
    75. }
    76. // ...
    77. }
    78. }
    79. }

向被调用端UIAbility发送约定序列化数据

  1. 向被调用端发送Parcelable数据有两种方式,一种是不带返回值,一种是获取被调用端返回的数据,method以及序列化数据需要与被调用端协商一致。如下示例调用Call接口,向Callee被调用端发送数据。

    收起
    自动换行
    深色代码主题
    复制
    1. import { UIAbility, Caller } from '@kit.AbilityKit';
    2. import { rpc } from '@kit.IPCKit';
    3. import { hilog } from '@kit.PerformanceAnalysisKit';
    4. const TAG: string = '[CalleeAbility]';
    5. const DOMAIN_NUMBER: number = 0xFF00;
    6. const MSG_SEND_METHOD: string = 'CallSendMsg';
    7. class MyParcelable {
    8. num: number = 0;
    9. str: string = '';
    10. constructor(num: number, string: string) {
    11. this.num = num;
    12. this.str = string;
    13. }
    14. mySequenceable(num: number, string: string): void {
    15. this.num = num;
    16. this.str = string;
    17. }
    18. marshalling(messageSequence: rpc.MessageSequence): boolean {
    19. messageSequence.writeInt(this.num);
    20. messageSequence.writeString(this.str);
    21. return true;
    22. }
    23. unmarshalling(messageSequence: rpc.MessageSequence): boolean {
    24. this.num = messageSequence.readInt();
    25. this.str = messageSequence.readString();
    26. return true;
    27. }
    28. }
    29. export default class EntryAbility extends UIAbility {
    30. // ...
    31. caller: Caller | undefined;
    32. async onButtonCall(): Promise<void> {
    33. try {
    34. let msg: MyParcelable = new MyParcelable(1, 'origin_Msg');
    35. if (this.caller) {
    36. await this.caller.call(MSG_SEND_METHOD, msg);
    37. }
    38. } catch (error) {
    39. hilog.error(DOMAIN_NUMBER, TAG, `caller call failed with ${error}`);
    40. }
    41. }
    42. // ...
    43. }
  2. 如下示例调用callWithResult()接口,向Callee被调用端发送待处理的数据originMsg,并将CallSendMsg方法处理完毕的数据赋值给backMsg。

    收起
    自动换行
    深色代码主题
    复制
    1. import { UIAbility, Caller } from '@kit.AbilityKit';
    2. import { rpc } from '@kit.IPCKit';
    3. import { hilog } from '@kit.PerformanceAnalysisKit';
    4. const TAG: string = '[CalleeAbility]';
    5. const DOMAIN_NUMBER: number = 0xFF00;
    6. const MSG_SEND_METHOD: string = 'CallSendMsg';
    7. let originMsg: string = '';
    8. let backMsg: string = '';
    9. class MyParcelable {
    10. num: number = 0;
    11. str: string = '';
    12. constructor(num: number, string: string) {
    13. this.num = num;
    14. this.str = string;
    15. }
    16. mySequenceable(num: number, string: string): void {
    17. this.num = num;
    18. this.str = string;
    19. }
    20. marshalling(messageSequence: rpc.MessageSequence): boolean {
    21. messageSequence.writeInt(this.num);
    22. messageSequence.writeString(this.str);
    23. return true;
    24. }
    25. unmarshalling(messageSequence: rpc.MessageSequence): boolean {
    26. this.num = messageSequence.readInt();
    27. this.str = messageSequence.readString();
    28. return true;
    29. }
    30. }
    31. export default class EntryAbility extends UIAbility {
    32. // ...
    33. caller: Caller | undefined;
    34. async onButtonCallWithResult(originMsg: string, backMsg: string): Promise<void> {
    35. try {
    36. let msg: MyParcelable = new MyParcelable(1, originMsg);
    37. if (this.caller) {
    38. const data = await this.caller.callWithResult(MSG_SEND_METHOD, msg);
    39. hilog.info(DOMAIN_NUMBER, TAG, 'caller callWithResult succeed');
    40. let result: MyParcelable = new MyParcelable(0, '');
    41. data.readParcelable(result);
    42. backMsg = result.str;
    43. hilog.info(DOMAIN_NUMBER, TAG, `caller result is [${result.num}, ${result.str}]`);
    44. }
    45. } catch (error) {
    46. hilog.error(DOMAIN_NUMBER, TAG, `caller callWithResult failed with ${error}`);
    47. }
    48. }
    49. // ...
    50. }

释放Caller通信接口

Caller不再使用后,应用开发者可以通过release()接口释放Caller。

收起
自动换行
深色代码主题
复制
  1. import { UIAbility, Caller } from '@kit.AbilityKit';
  2. import { hilog } from '@kit.PerformanceAnalysisKit';
  3. const TAG: string = '[CalleeAbility]';
  4. const DOMAIN_NUMBER: number = 0xFF00;
  5. export default class EntryAbility extends UIAbility {
  6. caller: Caller | undefined
  7. releaseCall(): void {
  8. try {
  9. if (this.caller) {
  10. this.caller.release();
  11. this.caller = undefined;
  12. }
  13. hilog.info(DOMAIN_NUMBER, TAG, 'caller release succeed');
  14. } catch (error) {
  15. hilog.error(DOMAIN_NUMBER, TAG, `caller release failed with ${error}`);
  16. }
  17. }
  18. }