HarmonyOS事件通信能力解决方案

通信场景

能力支持

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

HarmonyOS
2024-07-24 10:26:05
浏览
收藏 0
回答 1
待解决
回答 1
按赞同
/
按时间
五行缺踹

场景一:同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); 
}

1. 发送方:使用eventHub.emit触发打开弹窗事件。

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

2. 取消订阅事件。

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实现。

分享
微博
QQ
微信
回复
2024-07-24 20:45:02
相关问题
HarmonyOS代码封装解决方案
110浏览 • 1回复 待解决
HarmonyOS有访问相册有解决方案
56浏览 • 1回复 待解决
高级图表实现解决方案
241浏览 • 1回复 待解决
HarmonyOS 音视频处理相关解决方案
88浏览 • 1回复 待解决
抓包应用,求解决方案
1458浏览 • 1回复 待解决
lazyforeach替换数据源解决方案
328浏览 • 1回复 待解决
应用包体积大小优化解决方案
156浏览 • 1回复 待解决
图片存储解决方案谁知道啊?
2225浏览 • 1回复 待解决
webview中跨域问题解决方案
1135浏览 • 1回复 待解决
图像处理库是否有替代的解决方案
63浏览 • 0回复 待解决
List滚动条时长时短,求解决方案
697浏览 • 1回复 待解决
Harmony API9之后 GIS 解决方案有哪些?
1930浏览 • 1回复 待解决
HarmonyOS 线程通信 事件循环问题
35浏览 • 1回复 待解决
基于TLSSocket的通信能力
166浏览 • 1回复 待解决