当前位置: 首页 > news >正文

鸿蒙 EventBus 与 Message 通信机制详解

鸿蒙 EventBus 与 Message 通信机制详解

适用版本:HarmonyOS NEXT / API 12+
语言:ArkTS


一、为什么需要事件通信?

在鸿蒙应用开发中,组件之间、页面之间、线程之间经常需要传递数据或通知状态变化。直接持有引用会造成强耦合,因此需要专门的通信机制。

鸿蒙提供了两套主流方案:

机制核心 API适用场景
EventBus(事件总线)@ohos.events.emitter/context.eventHub同进程内组件、页面之间解耦通信
Message(消息传递)WorkerpostMessage/ TaskPool主线程与子线程之间的跨线程通信

二、EventBus:进程内事件总线

2.1 Emitter —— 全局事件总线

emitter是鸿蒙提供的内置事件总线,基于发布-订阅模式,订阅者和发布者完全解耦。

引入方式

import{emitter}from'@kit.BasicServicesKit';
订阅事件
// 定义事件 ID(number 类型)constLOGIN_SUCCESS_EVENT:emitter.InnerEvent={eventId:1001,};// 订阅(持久监听)emitter.on(LOGIN_SUCCESS_EVENT,(data:emitter.EventData)=>{constuserId:string=data.data?.['userId']asstring??'';console.info(`收到登录成功事件,userId:${userId}`);});// 订阅(只接收一次,自动取消)emitter.once(LOGIN_SUCCESS_EVENT,(data:emitter.EventData)=>{console.info('只接收一次的登录事件');});
发布事件
// 发布事件并携带数据emitter.emit(LOGIN_SUCCESS_EVENT,{data:{userId:'user_123',nickname:'张三',}});
取消订阅
// 取消对某个事件的所有订阅emitter.off(1001);
带优先级发布
constevent:emitter.InnerEvent={eventId:1002,priority:emitter.EventPriority.HIGH,// HIGH / LOW / IMMEDIATE / IDLE};emitter.emit(event,{data:{msg:'高优先级消息'}});

2.2 EventHub —— UIAbility 内部事件总线

EventHub绑定在UIAbilityContext上,事件生命周期随 UIAbility 结束而结束,更适合 UIAbility 内部的页面间通信。

// 在 UIAbility 中发布import{UIAbility,Want,AbilityConstant}from'@kit.AbilityKit';exportdefaultclassEntryAbilityextendsUIAbility{onForeground(){// 发布事件this.context.eventHub.emit('userLogout',{reason:'主动退出'});}}
// 在页面中订阅import{common}from'@kit.AbilityKit';@Entry@ComponentV2struct ProfilePage{privatecontext=getContext(this)ascommon.UIAbilityContext;aboutToAppear():void{this.context.eventHub.on('userLogout',(data:Record<string,string>)=>{console.info(`用户退出,原因:${data.reason}`);});}aboutToDisappear():void{// 页面销毁时一定要取消订阅!this.context.eventHub.off('userLogout');}}

2.3 自定义 EventBus(TypeScript 实现)

项目中也可以封装一个轻量 EventBus 单例:

typeEventCallback=(data:object)=>void;classEventBus{privatestaticinstance:EventBus;privatelisteners:Map<string,EventCallback[]>=newMap();staticgetInstance():EventBus{if(!EventBus.instance){EventBus.instance=newEventBus();}returnEventBus.instance;}on(event:string,callback:EventCallback):void{if(!this.listeners.has(event)){this.listeners.set(event,[]);}this.listeners.get(event)!.push(callback);}off(event:string,callback?:EventCallback):void{if(!callback){this.listeners.delete(event);return;}constcbs=this.listeners.get(event)??[];this.listeners.set(event,cbs.filter(cb=>cb!==callback));}emit(event:string,data:object={}):void{constcbs=this.listeners.get(event)??[];cbs.forEach(cb=>cb(data));}}exportconsteventBus=EventBus.getInstance();

使用:

// 订阅eventBus.on('refresh',(data)=>{console.info('刷新列表',JSON.stringify(data));});// 发布eventBus.emit('refresh',{tabId:'recommend'});// 取消eventBus.off('refresh');

三、Message:跨线程消息传递

Message机制用于主线程与子线程之间通信,核心是WorkerTaskPoolpostMessage/sendData

重要:ArkTS 中不同线程有各自独立的内存空间,不能共享对象引用,只能通过消息序列化传递数据。

3.1 Worker + postMessage

Worker 适合需要长期驻留、双向通信的子线程场景(如持续的 WebSocket 连接、音视频处理等)。

目录结构

entry/src/main/ets/ ├── pages/ │ └── Index.ets ← 主线程页面 └── workers/ └── MyWorker.ets ← Worker 子线程

Worker 子线程(MyWorker.ets)

import{worker,MessageEvents,ErrorEvent}from'@kit.ArkTS';// 获取当前 Worker 的通信端口constworkerPort:worker.ThreadWorkerGlobalScope=worker.workerPort;// 监听主线程消息workerPort.onmessage=(event:MessageEvents)=>{constdata=event.dataasRecord<string,string>;console.info(`[Worker] 收到主线程消息:${JSON.stringify(data)}`);// 处理耗时任务...constresult=heavyTask(data.input);// 回传结果给主线程workerPort.postMessage({result,status:'success'});};workerPort.onerror=(err:ErrorEvent)=>{console.error(`[Worker] 错误:${err.message}`);};functionheavyTask(input:string):string{// 模拟耗时计算returninput.toUpperCase();}

主线程(Index.ets)

import{worker,MessageEvents}from'@kit.ArkTS';@Entry@ComponentV2struct Index{privatemyWorker:worker.ThreadWorker|null=null;@Localresult:string='';aboutToAppear():void{// 创建 Workerthis.myWorker=newworker.ThreadWorker('entry/ets/workers/MyWorker.ets');// 监听 Worker 回传的消息this.myWorker.onmessage=(event:MessageEvents)=>{constdata=event.dataasRecord<string,string>;this.result=data.result;console.info(`[主线程] Worker 回传:${this.result}`);};}aboutToDisappear():void{// 页面销毁时终止 Worker,释放资源this.myWorker?.terminate();}build(){Column({space:16}){Text(`处理结果:${this.result}`)Button('发送任务给 Worker').onClick(()=>{// 向 Worker 发送消息this.myWorker?.postMessage({input:'hello harmony'});})}.width('100%').height('100%').justifyContent(FlexAlign.Center)}}

3.2 TaskPool + sendData(推荐)

TaskPool 是更现代的方案,适合一次性并发任务,由系统自动管理线程池,开发者无需手动创建/销毁线程。

import{taskpool}from'@kit.ArkTS';// 定义任务函数(必须是顶层函数或静态方法,不能是箭头函数)@ConcurrentfunctionprocessData(input:string):string{// 在子线程中执行returninput.split('').reverse().join('');}// 主线程调用asyncfunctionrunTask():Promise<void>{consttask=newtaskpool.Task(processData,'hello');// 等待任务完成并获取结果constresult:string=awaittaskpool.execute(task)asstring;console.info(`任务结果:${result}`);// 输出: olleh}

TaskPool 发送中间消息(sendData)

当任务需要在执行过程中不断向主线程汇报进度时:

// 子线程任务@ConcurrentfunctiondownloadFile(url:string):void{for(leti=0;i<=100;i+=10){// 向主线程发送进度taskpool.Task.sendData(i);// 模拟耗时}}// 主线程consttask=newtaskpool.Task(downloadFile,'https://example.com/file.zip');// 注册进度回调task.onReceiveData((progress:number)=>{console.info(`下载进度:${progress}%`);});taskpool.execute(task);

四、EventBus vs Message 核心区别

对比维度EventBus(emitter / EventHub)Message(Worker / TaskPool)
通信范围同一线程(主线程)内的组件/页面间主线程 ↔ 子线程(跨线程)
数据共享可直接传递对象引用数据需要可序列化,不能传引用
生命周期需手动取消订阅,否则内存泄漏Worker 需手动 terminate,TaskPool 自动管理
使用场景页面刷新、登录状态同步、Tab切换通知图片处理、加密运算、大数据解析等耗时任务
是否阻塞 UI不阻塞(同线程但异步回调)不阻塞(任务在子线程执行)
线程安全单线程,无需考虑需注意数据竞争,ArkTS 通过隔离内存保证安全
复杂度低,几行代码即可使用较高,需要额外的 Worker 文件

五、选型建议

需要通信? │ ├── 是否涉及耗时计算(>16ms 会卡 UI)? │ ├── 是 → 使用 TaskPool(一次性任务)或 Worker(持续任务) │ └── 否 → 继续往下 │ ├── 是否跨 UIAbility? │ ├── 是 → 使用 emitter(全局) │ └── 否 → 同一 UIAbility 内 │ └── 是否需要随 UIAbility 自动清理? ├── 是 → 使用 EventHub(context.eventHub) └── 否 → 使用 emitter 或自定义 EventBus

典型场景对照

场景推荐方案
用户登录后刷新首页emitter.emit
购物车数量更新通知各页面emitter或自定义 EventBus
退出登录清理页面状态context.eventHub.emit
大图压缩/格式转换TaskPool
实时音视频数据处理Worker
WebSocket 长连接管理Worker(常驻子线程)
批量数据加密上传TaskPool(并发多任务)

六、常见坑点

EventBus 篇

坑 1:忘记取消订阅导致内存泄漏

// ❌ 错误:aboutToDisappear 中没有取消订阅aboutToAppear():void{emitter.on({eventId:1001},()=>{...});}// ✅ 正确:配对 offaboutToAppear():void{emitter.on({eventId:1001},this.onEvent);}aboutToDisappear():void{emitter.off(1001);}

坑 2:eventId 冲突

全局使用emitter时,不同模块可能使用相同的 eventId,建议统一在常量文件中定义:

// events/EventIds.tsexportconstEventIds={LOGIN_SUCCESS:1001,CART_UPDATE:1002,REFRESH_HOME:1003,};

Message 篇

坑 3:Worker 中不能使用 UI 相关 API

Worker 线程没有 UI 上下文,不能调用routerpromptActionAppStorage等 API。

坑 4:传递不可序列化的对象

// ❌ 错误:传递了含函数的对象worker.postMessage({callback:()=>{}});// 报错// ✅ 正确:只传可序列化的纯数据worker.postMessage({userId:'123',action:'refresh'});

坑 5:@Concurrent 函数不能捕获外部变量

constprefix='hello';// 外部变量// ❌ 错误:@Concurrent 函数不能闭包捕获外部变量@ConcurrentfunctionbadTask():string{returnprefix+' world';// 编译报错}// ✅ 正确:通过参数传入@ConcurrentfunctiongoodTask(prefix:string):string{returnprefix+' world';}taskpool.execute(newtaskpool.Task(goodTask,prefix));

七、总结

  • EventBus(emitter / EventHub)本质是主线程内的发布-订阅模式,解决的是组件解耦问题,代码简单,但要注意及时取消订阅。
  • Message(Worker / TaskPool)本质是跨线程通信,解决的是耗时任务不阻塞 UI的问题,数据需要可序列化。

两者并不互斥,实际项目中常常配合使用:Worker 在子线程完成计算后,通过postMessage回传结果,主线程再通过emitter通知相关组件更新 UI。

http://www.jsqmd.com/news/1079802/

相关文章:

  • 【回眸】Agent-Studio 智能体开发与应用实战指南
  • 不用再抱着摄像头调试了!国标GB28181设备端EasyGBD Windows桌面版,国标开发效率直接拉满
  • 视频去水印软件推荐:亲测横评,免费好用的电脑手机与在线方案一次说清
  • Ricon组态 - 让数据可视化如此简单
  • 2026年AI智能体培训赛道深度评测:从低代码平台到业务落地的全链路实践
  • AI/Vibe Coding,本质是软件人工时代向软件工业时代发展
  • 哨兵机制Sentinel集群搭建
  • Java计算机毕设之基于 SpringBoot 的住宿订单统计与客房管理系统设计与实现 中小型酒店客房运维与入住服务系统设计与实现(完整前后端代码+说明文档+LW,调试定制等)
  • 基于Qwen3-4B与OpenClaw的AI智能体UI自动化测试实践
  • 计算机小程序毕设实战-基于 SpringBoot 的校园社团信息化管理平台设计与实现 面向高校师生的社团运营管理小程序系统设计与实现【完整源码+LW+部署说明+演示视频,全bao一条龙等】
  • 【无标题】补充章节:反物质的拓扑路径起源
  • 实现 Tab 切换面板(动态组件)Demo
  • WISV:无线感知语义验证如何加速边缘LLM分布式推理
  • C#:回车换行
  • 云原生 AI 平台:从模型仓库到弹性推理服务的全链路搭建
  • 一文读懂CUTTag:表观遗传研究的“精准定位神器”
  • 从Goncharov猜想到Bloch-Kriz构造:混合Tate动机与余李代数
  • 用 “WeChat AI Skill Builder“快速开发微信小程序「AI 能力」功能
  • 安居客App逆向分析:从抓包到参数签名算法还原实战
  • Codex 提示词工程——写出让 Codex 一次理解对的高效 Prompt
  • JiYuTrainer深度解析:破解极域电子教室控制的技术艺术
  • 草本外用养护货源怎么选?名氏草本舒缓贴全维度解析
  • 信号拟合框架sigfit:从数据到模型的工程实践指南
  • 【课程设计/毕业设计】基于 SpringBoot + 小程序的美妆电商综合管理平台 数字化美妆门店线上服务小程序 美妆商品智能推荐与购物交易系统设计与实现【附源码、数据库、万字文档】
  • 企业数据安全警报:为什么70%的组织都在担心数据泄露?
  • 自动驾驶仿真专用自动曝光白平衡ISP仿真Shader
  • 港口监控每天产生10万小时视频,90%都在“白看”?国标GB28181视频平台EasyGBS这套AI方案让安全隐患无处遁形
  • 创客匠人:私域直播如何搭建知识 IP 可持续变现体系
  • 使用 Docker Compose 部署 Dify
  • JS、浏览器——栈和队列(事件循环相关)