场景描述
元能力和事件通知当前提供的通信方式主要有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;
- 订阅方:创建自定义弹窗中显示的组件内容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);
}
- 发送方:使用eventHub.emit触发打开弹窗事件。
this.uiAbilityContext.eventHub.emit('openDialog');
- 取消订阅事件。
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);
}
- 发送方,eventHub.emit传递数据。
@Observed
class Dog {
public age: number;
constructor(size: number) {
this.age = ageID++;
}
}
this.applicationContext.eventHub.emit("myEvent", this.dog);
- 取消订阅。
this.applicationContext.eventHub.off('myEvent');
场景三:线程间通信
worker线程执行字符串倒序
效果图
方案
- 在对应目录下鼠标右键 > New > Worker,新建Worker线程目录及文件,或新建worker.ets文件手动在build-profile.json5添加如下配置。
"buildOption": {
"sourceOption": {
"workers": [
"./src/main/ets/model/Worker.ts",
]
}
}
- 通过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实现字符串排序
效果图
方案
-
使用emitter.on监听事件,当触发事件后,弹出弹窗并将收到的数据eventData显示在弹窗上。
-
调用sort()对输入字符串数组排序,排序完成后通过emitter.emit将排序后的数据传递。
-
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自定义公共事件
效果图
方案
-
发布方定义CommonEventPublishData,设置订阅者包名,通过commonEventManager.publish发布自定义公共事件。
-
订阅方使用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 NEXT)文档用来跟着学习是非常有必要的。
这份鸿蒙(HarmonyOS NEXT)文档包含了鸿蒙开发必掌握的核心知识要点,内容包含了(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、OpenHarmony南向开发、鸿蒙项目实战等等)鸿蒙(HarmonyOS NEXT)技术知识点。
希望这一份鸿蒙学习文档能够给大家带来帮助,有需要的小伙伴自行领取,限时开源,先到先得~无套路领取!!
获取这份完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习文档
鸿蒙(HarmonyOS NEXT)5.0最新学习路线
有了路线图,怎么能没有学习文档呢,小编也准备了一份联合鸿蒙官方发布笔记整理收纳的一套系统性的鸿蒙(OpenHarmony )学习手册(共计1236页)与鸿蒙(OpenHarmony )开发入门教学视频,内容包含:ArkTS、ArkUI、Web开发、应用模型、资源分类…等知识点。
获取以上完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习文档
《鸿蒙 (OpenHarmony)开发入门教学视频》
《鸿蒙生态应用开发V3.0白皮书》
《鸿蒙 (OpenHarmony)开发基础到实战手册》
OpenHarmony北向、南向开发环境搭建
《鸿蒙开发基础》
●ArkTS语言
●安装DevEco Studio
●运用你的第一个ArkTS应用
●ArkUI声明式UI开发
.……
《鸿蒙开发进阶》
●Stage模型入门
●网络管理
●数据管理
●电话服务
●分布式应用开发
●通知与窗口管理
●多媒体技术
●安全技能
●任务管理
●WebGL
●国际化开发
●应用测试
●DFX面向未来设计
●鸿蒙系统移植和裁剪定制
……
《鸿蒙进阶实战》
●ArkTS实践
●UIAbility应用
●网络案例
……