前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >HarmonyOS 开发实践 —— 事件通信能力解决方案

HarmonyOS 开发实践 —— 事件通信能力解决方案

原创
作者头像
小帅聊鸿蒙
发布2024-12-01 19:16:24
发布2024-12-01 19:16:24
2380
举报
文章被收录于专栏:鸿蒙开发笔记鸿蒙开发笔记

场景描述

通信场景

能力支持

同Ability通信

Emitter、EventHub、CommonEvent

跨Ability通信

Emitter、EventHub、CommonEvent

跨线程通信

Emitter、CommonEvent、Worker、Taskpool

跨进程通信

CommonEvent、IPC&RPC

元能力和事件通知当前提供的通信方式主要有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上下文,才能正常打开弹窗。

核心代码

代码语言:ts
复制
private uiAbilityContext = getContext() as common.UIAbilityContext;

1.订阅方:创建自定义弹窗中显示的组件内容buildText,使用openCustomDialog打开弹窗,eventHub.on订阅弹窗事件。

代码语言:ts
复制
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触发打开弹窗事件。

代码语言:ts
复制
this.uiAbilityContext.eventHub.emit('openDialog');

3.取消订阅事件。

代码语言:ts
复制
this.uiAbilityContext.eventHub.off('openDialog');

场景二:跨 Ability 通信

使用 EventHub 进行数据通信

效果图

方案

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

核心代码

代码语言:ts
复制
this.uiAbilityContext.eventHub.off('openDialog');

1.订阅方:eventHub.on订阅消息,当收到消息时打开弹窗。

代码语言:ts
复制
eventFunc(arg: Dog) {
  promptAction.showDialog({
    'message': 'dog age is ' + arg.age
  });
}
 
aboutToAppear(): void {
  this.applicationContext.eventHub.on('myEvent', this.eventFunc);
}

2.发送方,eventHub.emit传递数据。

代码语言:ts
复制
@Observed
class Dog {
  public age: number;
 
  constructor(size: number) {
    this.age = ageID++;
  }
}
 
this.applicationContext.eventHub.emit("myEvent", this.dog);

3.取消订阅。

代码语言:ts
复制
this.applicationContext.eventHub.off('myEvent');

场景三:线程间通信

worker 线程执行字符串倒序

效果图

方案

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

代码语言:ts
复制
"buildOption": {
  "sourceOption": {
    "workers": [
    "./src/main/ets/model/Worker.ts",
    ]
  }
}

2.通过postMessage向worker线程传递字符串,worker线程将字符串倒序后,主线程再通过onmessage接收倒序后的字符串。

核心代码

代码语言:ts
复制
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执行创建好的任务,执行完成后将排序后的字符串同步到输出框。

核心代码

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

代码语言:ts
复制
emitter.on("eventId", (eventData: emitter.EventData) => {
  promptAction.showToast({
    message: 'receive' + eventData.data?.content,
    duration: 2000
  });
})

启动任务池taskpool执行任务。

代码语言:ts
复制
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();
}

字符串排序并触发事件。

代码语言:ts
复制
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.用户点击通知栏上的通知,会自动拉起对应的应用。

核心代码

发布方:

代码语言:ts
复制
// 公共事件相关信息
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');
  }
});

订阅方:

代码语言:ts
复制
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:

代码语言:ts
复制
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实现。

写在最后

如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:

  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力;
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识;
  • 想要获取更多完整鸿蒙最新学习知识点,可关注B站:码牛课堂;

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 场景描述
    • 能力对比:
    • 场景一:同 Ability 通信
    • 场景二:跨 Ability 通信
    • 场景三:线程间通信
    • taskpool 实现字符串排序
    • 场景四:进程间通信
  • 写在最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档