如何实现EmbeddedComponent和EmbeddedUIExtensionAbility之间的双向通信
=======================================================

#### 问题现象

EmbeddedComponent和EmbeddedUIExtensionAbility之间进程隔离，目前官网[场景示例](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-embedded-components#场景示例)只提供了[terminateSelfWithResult](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-inner-application-uiabilitycontext#terminateselfwithresult)方法，没有提供更多资料参考，如何实现EmbeddedComponent和EmbeddedUIExtensionAbility之间的双向通信？

#### 背景知识

*   [EmbeddedComponent](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-container-embedded-component)：EmbeddedComponent组件用于在当前页面嵌入本应用内其他EmbeddedUIExtensionAbility提供的UI。
*   [EmbeddedUIExtensionAbility](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-app-ability-embeddeduiextensionability)：提供方应用中定义使用，用于实现跨进程界面嵌入功能，仅能被同应用的UIAbility拉起，并需在多进程权限的场景下使用。
*   [@ohos.commonEventManager (公共事件模块)](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-commoneventmanager)：提供了公共事件相关的能力，包括发布公共事件、订阅公共事件、以及退订公共事件，可用于实现跨进程的事件通信能力。

#### 解决方案

EmbeddedComponent和EmbeddedUIExtensionAbility属于两个进程，可以利用commonEventManager进行双向通信，详细步骤如下：

1.  在项目的ets/extensionAbility（extensionAbility目录需要手动创建）目录下新建EmbeddedUIExtensionAbility，代码如下：
    
    import { EmbeddedUIExtensionAbility, UIExtensionContentSession, Want } from '@kit.AbilityKit';
    
    
    const TAG: string = '\[ExampleEmbeddedAbility\]';
    
    
    export default class ExampleEmbeddedAbility extends EmbeddedUIExtensionAbility {
      onCreate() {
        console.info(TAG, \`onCreate\`);
      }
    
    
      onForeground() {
        console.info(TAG, \`onForeground\`);
      }
    
    
      onBackground() {
        console.info(TAG, \`onBackground\`);
      }
    
    
      onDestroy() {
        console.info(TAG, \`onDestroy\`);
      }
    
    
      onSessionCreate(want: Want, session: UIExtensionContentSession) {
        try {
          console.info(TAG, \`onSessionCreate, want: ${JSON.stringify(want)}\`);
        } catch (error) {
          console.info(TAG, \`error\`);
        }
        let param: Record<string, UIExtensionContentSession> = {
          'session': session
        };
        let storage: LocalStorage = new LocalStorage(param);
      _  // 加载pages/extension.ets页面内容_
        session.loadContent('pages/Extension', storage);
      }
    
    
      onSessionDestroy() {
        console.info(TAG, \`onSessionDestroy\`);
      }
    };
    
2.  在项目main\_pages.json中增加声明Extension：
    
    {
      "src": \[
        "pages/Index",
        "pages/Extension"
      \]
    }
    
3.  在module.json5配置文件的"extensionAbilities"标签下增加ExampleEmbeddedAbility配置：
    
    {
      "module": {
        "name": "entry",
        "type": "entry",
        "description": "$string:module\_desc",
        "mainElement": "EntryAbility",
        "deviceTypes": \[
          "tablet",
          "2in1"
        \],
        "deliveryWithInstall": true,
        "installationFree": false,
        "pages": "$profile:main\_pages",
        "abilities": \[
          {
            "name": "EntryAbility",
            "srcEntry": "./ets/entryability/EntryAbility.ets",
            "description": "$string:EntryAbility\_desc",
            "icon": "$media:layered\_image",
            "label": "$string:EntryAbility\_label",
            "startWindowIcon": "$media:startIcon",
            "startWindowBackground": "$color:start\_window\_background",
            "exported": true,
            "skills": \[
              {
                "entities": \[
                  "entity.system.home"
                \],
                "actions": \[
                  "ohos.want.action.home"
                \]
              }
            \]
          }
        \],
        "extensionAbilities": \[
          {
            "name": "EntryBackupAbility",
            "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets",
            "type": "backup",
            "exported": false,
            "metadata": \[
              {
                "name": "ohos.extension.backup",
                "resource": "$profile:backup\_config"
              }
            \],
          },
          {
            "name": "ExampleEmbeddedAbility",
            "srcEntry": "./ets/extensionAbility/ExampleEmbeddedAbility.ets",
            "type": "embeddedUI"
          }
        \]
      }
    }
    
4.  在Index页面中实现主页面UI布局并且接收来自EmbeddedUIExtensionAbility的信息：
    
    import { Want } from '@kit.AbilityKit';
    import { BusinessError, commonEventManager } from '@kit.BasicServicesKit';
    
    
    
    
    let options: commonEventManager.CommonEventPublishData = {
      code: 0,
      data: 'Hello',
      isOrdered: true _// 有序公共事件_
    };
    
    
    @Entry
    @Component
    struct Index {
      @State message: string = 'Message from ExampleEmbeddedAbility: ';
      private want: Want = {
        bundleName: 'com.example.mycommon',
        abilityName: 'ExampleEmbeddedAbility',
      };
    
    
      aboutToAppear(): void {
      _  // 定义订阅者，用于保存创建成功的订阅者对象，后续使用其完成订阅及退订的动作_
        let subscriber: commonEventManager.CommonEventSubscriber;
        _// 订阅者信息_
        let subscribeInfo: commonEventManager.CommonEventSubscribeInfo = {
          events: \['event'\]
        };
    
    
     _   // 创建订阅者_
        try {
          commonEventManager.createSubscriber(subscribeInfo,
            (err: BusinessError, commonEventSubscriber: commonEventManager.CommonEventSubscriber) => {
              if (!err) {
                console.info(\`Succeeded in creating subscriber.\`);
                subscriber = commonEventSubscriber;
             _   // 订阅公共事件_
                try {
                  commonEventManager.subscribe(subscriber,
                    (err: BusinessError, data: commonEventManager.CommonEventData) => {
                      if (err) {
                        console.error(\`Failed to subscribe. Code is ${err.code}, message is ${err.message}\`);
                        return;
                      }
                      let receiveData = JSON.parse(JSON.stringify(data)) as commonEventManager.CommonEventPublishData;
                      this.message += receiveData.data;
                      console.info(\`Succeeded in subscribing, data is ${JSON.stringify(data)}\`);
                    });
                } catch (error) {
                  let err: BusinessError = error as BusinessError;
                  console.error(\`Failed to subscribe. Code is ${err.code}, message is ${err.message}\`);
                }
                return;
              }
              console.error(\`Failed to create subscriber. Code is ${err.code}, message is ${err.message}\`);
            });
        } catch (error) {
          let err: BusinessError = error as BusinessError;
          console.error(\`Failed to create subscriber. Code is ${err.code}, message is ${err.message}\`);
        }
      }
    
    
      build() {
        Row() {
          Column() {
            Text(this.message).fontSize(20);
            Button('发送数据')
              .width('80%')
              .onClick(() => {
                try {
                  commonEventManager.publish('event2', options, (err: BusinessError) => {
                    if (err) {
                      console.error(\`Failed to publish common event. Code is ${err.code}, message is ${err.message}\`);
                      return;
                    }
                    console.info(\`Succeeded in publishing common event.\`);
                  });
                } catch (error) {
                  let err: BusinessError = error as BusinessError;
                  console.error(\`Failed to publish common event. Code is ${err.code}, message is ${err.message}\`);
                }
              });
            Text('EmbeddedComponent').fontSize(20);
          }
          .width('50%')
          .height(300)
          .justifyContent(FlexAlign.SpaceBetween)
          .borderColor(Color.Orange)
          .borderWidth(2);
    
    
          Column() {
            EmbeddedComponent(this.want, EmbeddedType.EMBEDDED\_UI\_EXTENSION)
              .width('100%')
              .height('100%')
              .onError((error) => {
              _  // 失败或异常触发onError回调，文本框显示如下报错内容_
                this.message = 'Error: code = ' + error.code;
              });
          }
          .width('50%')
          .height(300)
          .borderColor(Color.Blue)
          .borderWidth(2)
          .margin({ left: 5 });
    
    
        }
        .height('100%')
        .width('100%');
    
    
      }
    }
    
    注意：bundleName需要和项目的包名一致。
    
5.  在Extension页面中实现EmbeddedComponent的UI布局并且发送事件信息：
    
    import { BusinessError, commonEventManager } from '@kit.BasicServicesKit';
    
    _// 公共事件相关信息，以发布有序公共事件为例_
    let options: commonEventManager.CommonEventPublishData = {
      code: 0,
      data: 'Good',
      isOrdered: true _// 有序公共事件_
    };
    
    
    @Entry
    @Component
    struct Extension {
      @State message: string = 'Message from EmbeddedComponent: ';
    
    
      aboutToAppear(): void {
       _ // 定义订阅者，用于保存创建成功的订阅者对象，后续使用其完成订阅及退订的动作_
        let subscriber: commonEventManager.CommonEventSubscriber;
     _   // 订阅者信息_
        let subscribeInfo: commonEventManager.CommonEventSubscribeInfo = {
          events: \['event2'\]
        };
    
    
      _  // 创建订阅者_
        try {
          commonEventManager.createSubscriber(subscribeInfo,
            (err: BusinessError, commonEventSubscriber: commonEventManager.CommonEventSubscriber) => {
              if (!err) {
                console.info(\`Succeeded in creating subscriber.\`);
                subscriber = commonEventSubscriber;
                /_/ 订阅公共事件_
                try {
                  commonEventManager.subscribe(subscriber,
                    (err: BusinessError, data: commonEventManager.CommonEventData) => {
                      if (err) {
                        console.error(\`Failed to subscribe. Code is ${err.code}, message is ${err.message}\`);
                        return;
                      }
                      let receiveData = JSON.parse(JSON.stringify(data)) as commonEventManager.CommonEventPublishData;
                      this.message += receiveData.data;
                      console.info(\`Succeeded in subscribing, data is ${JSON.stringify(data)}\`);
                    });
                } catch (error) {
                  let err: BusinessError = error as BusinessError;
                  console.error(\`Failed to subscribe. Code is ${err.code}, message is ${err.message}\`);
                }
                return;
              }
              console.error(\`Failed to create subscriber. Code is ${err.code}, message is ${err.message}\`);
            });
        } catch (error) {
          let err: BusinessError = error as BusinessError;
          console.error(\`Failed to create subscriber. Code is ${err.code}, message is ${err.message}\`);
        }
      }
    
    
      build() {
        Column() {
          Text(this.message).fontSize(20);
          Button('发送数据')
            .width('80%')
            .onClick(() => {
            _  // 发布公共事件_
              try {
                commonEventManager.publish('event', options, (err: BusinessError) => {
                  if (err) {
                    console.error(\`Failed to publish common event. Code is ${err.code}, message is ${err.message}\`);
                    return;
                  }
                  console.info(\`Succeeded in publishing common event.\`);
                });
              } catch (error) {
                let err: BusinessError = error as BusinessError;
                console.error(\`Failed to publish common event. Code is ${err.code}, message is ${err.message}\`);
              }
            });
          Text('ExampleEmbeddedAbility').fontSize(20);
        }.width('100%').height('100%').justifyContent(FlexAlign.SpaceBetween);
      }
    }