场景描述

<p id="p9439742151711">通信场景</p> <p id="p19439134218174">能力支持</p>
<p id="p543910426176">同Ability通信</p> <p id="p1343964261714">Emitter、EventHub、CommonEvent</p>
<p id="p8439154201718">跨Ability通信</p> <p id="p1344010426177">Emitter、EventHub、CommonEvent</p>
<p id="p14440124215171">跨线程通信</p> <p id="p2440144261716">Emitter、CommonEvent、Worker、Taskpool</p>
<p id="p204401342191710">跨进程通信</p> <p id="p444074217177">CommonEvent、IPC&RPC</p>

​ 元能力和事件通知当前提供的通信方式主要有Emitter、EventHub、CommonEvent,线程间通信也可以使用Worker和Taskpool提供的postMessage和sendData向数组线程发送消息。应用间通信可以使用自定义公共事件和IPC&RPC两种方式。本文主要介绍事件通知和元能力提供的通信能力。

​ 能力对比:

Emitter主要提供线程间发送和处理事件的能力,包括对持续订阅事件或单次订阅事件的处理、取消订阅事件、发送事件到事件队列等。FA与Stage模型都可以使用。

EventHub提供了一种基于发布订阅模式的事件机制,通过订阅和发布自定义事件,实现UIAbility组件/ExtensionAbility组件与UI之间的数据同步。通过context获取,多用于主线程通信。仅Stage模型可用。

CommonEvent为应用程序提供订阅、发布、退订公共事件的能力。可分为系统公共事件和自定义公共事件。系统公共事件指,系统内部定义的公共事件,如应用包安装、设备关机等。自定义公共事件可用于实现跨进程的事件通信能力。

方案描述

场景一:同Ability通信

通过Eventhub订阅事件打开自定义弹窗:

效果图

方案

​弹窗功能依赖UI的执行上下文,不可在UI上下文不明确的地方使用,在一些异步回调或非UI界面中调用该接口,可能会无法跟踪到当前UI的上下文,导致接口执行失败,不能正常打开弹窗。所以当使用Eventhub传递事件时需要使用promptAction.openCustomDialog保证拿到同一UI上下文,才能正常打开弹窗。

核心代码

private uiAbilityContext = getContext() as common.UIAbilityContext;
  1. 订阅方:创建自定义弹窗中显示的组件内容buildText,使用openCustomDialog打开弹窗,eventHub.on订阅弹窗事件。

    aboutToAppear(): void {
      this.uiAbilityContext.eventHub.on('openDialog', () => {
      this.openDialog('自定义弹窗');
    });
    }
    
    openDialog(str: string) {
      let uiContext = this.getUIContext();
      let promptAction = uiContext.getPromptAction();
      let contentNode = new ComponentContent(uiContext, wrapBuilder(buildText), new Params(str));
      promptAction.openCustomDialog(contentNode);
    }
  2. 发送方:使用eventHub.emit触发打开弹窗事件。

    this.uiAbilityContext.eventHub.emit('openDialog');
  3. 取消订阅事件。

    this.uiAbilityContext.eventHub.off('openDialog');

场景二:跨Ability通信

使用EventHub进行数据通信

效果图

方案

​EventHub使用的核心是要保证订阅方和发送方拿到同一个context,跨ability时可以通过applicationContext传递消息。Emitter不支持传递带有@标签的类(emitter支持的消息类型与worker相同,参考序列化支持类型),可以使用EventHub作为替代方案。

核心代码

private applicationContext = getContext().getApplicationContext();
  1. 订阅方:eventHub.on订阅消息,当收到消息时打开弹窗。

    eventFunc(arg: Dog) {
      promptAction.showDialog({
        'message': 'dog age is ' + arg.age
      });
    }
    
    aboutToAppear(): void {
      this.applicationContext.eventHub.on('myEvent', this.eventFunc);
    }
  2. 发送方,eventHub.emit传递数据。

    @Observed
    class Dog {
      public age: number;
    
      constructor(size: number) {
        this.age = ageID++;
      }
    }
    
    this.applicationContext.eventHub.emit("myEvent", this.dog);
  3. 取消订阅。

    this.applicationContext.eventHub.off('myEvent');

场景三:线程间通信

worker线程执行字符串倒序

效果图

方案

  1. 在对应目录下鼠标右键 \> New \> Worker,新建Worker线程目录及文件,或新建worker.ets文件手动在build-profile.json5添加如下配置。

    "buildOption": {
      "sourceOption": {
        "workers": [
        "./src/main/ets/model/Worker.ts",
        ]
      }
    }
  2. 通过postMessage向worker线程传递字符串,worker线程将字符串倒序后,主线程再通过onmessage接收倒序后的字符串。

核心代码

async executeWorkerFunc(inPutStr: string) {
  //判断输入是否为空
  if (!this.jsWorkerInPutStr.length) {
    this.jsWorkerOutPutStr = "No input for the string to be reserved.\n";
    return;
  }
  this.myWorker.postMessage(inPutStr);//主线程向worker线程传递消息
  let strFlag = false;
  let outPutStr = '';
  //主线程接收worker线程消息
  this.myWorker.onmessage = (e) => {
    outPutStr = e.data.toString();
    strFlag = true;
  }
  this.jsWorkerOutPutStr = outPutStr;
}
// worker.ets
let workerPort: ThreadWorkerGlobalScope = worker.workerPort;

//接收来自主线程的消息
workerPort.onmessage = (e: MessageEvents) => {
  let oldData : string = e.data;
  let newData = oldData.split("").reverse().join("");  //将字符串倒序
  workerPort.postMessage(newData);  //将处理结果返回主线程
}

taskpool实现字符串排序

效果图

方案

  1. ​使用emitter.on监听事件,当触发事件后,弹出弹窗并将收到的数据eventData显示在弹窗上。
  2. 调用sort()对输入字符串数组排序,排序完成后通过emitter.emit将排序后的数据传递。
  3. taskpool.Task构造排序任务Task,然后使用taskpool.execute执行创建好的任务,执行完成后将排序后的字符串同步到输出框。

核心代码

​订阅事件,收到事件后弹出弹窗。

emitter.on("eventId", (eventData: emitter.EventData) => {
  promptAction.showToast({
    message: 'receive' + eventData.data?.content,
    duration: 2000
  });
})

启动任务池taskpool执行任务。

async executeImmediately() {
  if (!this.taskPoolInPutStr.length) {
    this.taskPoolOutPutStr = 'No input for the string to be sorted.\n';
    return;
  }
  // 创建task任务
  let task = new taskpool.Task(strSort, this.taskPoolInPutArr);
  this.taskPoolStack.push(task);
  // 将待执行的函数放入taskpool内部任务队列
  await taskpool.execute(task).then((result) => {
    this.taskPoolOutPutStr = `${this.taskPoolOutPutStr}Task executed successfully: `
    this.taskPoolOutPutStr += `Task executed successfully:${result.toString()}`;
  }).catch((e: Error) => {
    this.taskPoolOutPutStr += `Task executed failed:${e.toString()}`;
  });
  this.taskPoolStack.pop();
}

字符串排序并触发事件。

function strSort(inPutArr: string[]): string[] {
  let newArr = inPutArr.sort();
  let eventData: emitter.EventData = {
    data: {
      'content': JSON.stringify(newArr),
    }
  };
  emitter.emit('eventId', eventData)
  return newArr;
}

场景四:进程间通信

CommonEvent自定义公共事件

效果图

方案

  1. ​发布方定义CommonEventPublishData,设置订阅者包名,通过commonEventManager.publish发布自定义公共事件。
  2. 订阅方使用createSubscriber创建订阅者,并设置订阅者信息CommonEventSubscribeInfo,当收到公共事件后发布一条通知。

​ 自定义通知:

a.创建拉起应用的WantAgentInfo信息。

​b.调用getWantAgent()创建WantAgent。

​c.构造NotificationRequest对象,并发布携带WantAgent的通知。

​d.用户点击通知栏上的通知,会自动拉起对应的应用。

核心代码

​发布方:

// 公共事件相关信息
let options: CommonEventManager.CommonEventPublishData = {
  bundleName: 'com.example.mysubscriber', //表示订阅者包名称,只有包名为bundleName的订阅者才能收到该公共事件。
};

CommonEventManager.publish('eventTest', options, (err: Base.BusinessError) => {
  if (err) {
    hilog.error(0xFF00, LOG_TAG, `PublishCallBack err = ${JSON.stringify(err)}`);
  } else {
    hilog.info(0xFF00, LOG_TAG, 'commonEvent Publish success');
  }
});

订阅方:

let subscriber: CommonEventManager.CommonEventSubscriber; //用于保存创建成功的订阅者对象,后续使用其完成订阅及退订的动作
//订阅者信息
let subscribeInfo: CommonEventManager.CommonEventSubscribeInfo = {
  events: ['eventTest']
};

//订阅公共事件回调
function SubscribeCB(err: Base.BusinessError, data: CommonEventManager.CommonEventData) {
  if (err) {
    hilog.error(0xFF00, LOG_TAG, `subscribe failed, code is ${err.code}, message is ${err.message}`);
  } else {
    publishNotification();
    hilog.info(0xFF00, LOG_TAG, 'subscribe success');
  }
}

//创建订阅者回调
function createCB(err: Base.BusinessError, commonEventSubscriber: CommonEventManager.CommonEventSubscriber) {
  if (!err) {
    hilog.info(0xFF00, LOG_TAG, 'createSubscriber');
    subscriber = commonEventSubscriber;
    //订阅公共事件
    try {
      CommonEventManager.subscribe(subscriber, SubscribeCB);
    } catch (error) {
      let err: Base.BusinessError = error as Base.BusinessError;
      hilog.error(0xFF00, LOG_TAG, `subscribe failed, code is ${err.code}, message is ${err.message}`);
    }
  } else {
    hilog.error(0xFF00, LOG_TAG, `createSubscriber failed, code is ${err.code}, message is ${err.message}`);
  }
}

自定义通知publishNotification:

notificationManager.requestEnableNotification();//开启通知权限
async function publishNotification() {
  let wantAgent: _WantAgent;
  //WantAgentInfo对象
  let wantAgentInfo: WantAgent.WantAgentInfo = {
    wants: [
      {
        bundleName: 'com.example.mysubscriber',
        abilityName: 'EntryAbility',
      } as Want
    ],
    operationType: WantAgent.OperationType.START_ABILITIES,
    requestCode: 0,
    wantAgentFlags: [WantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
  };

  WantAgent.getWantAgent(wantAgentInfo).then((data) => {
    wantAgent = data;
    let notificationRequest: notificationManager.NotificationRequest = {
      content: {
        notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
        normal: {
          title: '自定义公共事件',
          text: '收到其他应用一条消息',
          additionalText: 'Test_AdditionalText',
        },
      },
      id: 6,
      tapDismissed: true, //通知是否自动清除
      notificationSlotType: notificationManager.SlotType.SOCIAL_COMMUNICATION, //社交类型通知
      label: 'Receive CommonEvent',
      wantAgent: wantAgent,
    };
    notificationManager.publish(notificationRequest);
  });
}

其它常见问题

1.粘性事件:

​ emitter对标Node.js,进程内消息分发,业界没有发布粘性的,不支持粘性。粘性事件可以考虑使用自定义公共事件实现。

2.事件处理优先级:

​ 当冷启动时间较长时,需要将一些低优先级任务在主线程空闲的时候去加载,避免阻塞UI线程,可以使用emitter定义事件EventPriority优先级为idle实现。


HarmonyOS码上奇行
7k 声望2.8k 粉丝