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