事件总线:实现发布/订阅模式的通信库(233) 📅 2026/7/22 17:52:41 在鸿蒙HarmonyOS原生应用开发中随着业务模块的增多组件间、页面间以及线程间的通信需求变得日益复杂。如果采用传统的层层回调或状态透传会导致代码高度耦合且难以维护。为此鸿蒙官方提供了一套完善的发布/订阅事件通信机制。针对不同的通信范围开发者主要使用以下三种核心方案一、 同UIAbility内的轻量级通信EventHubEventHub 是鸿蒙提供的一种基于发布-订阅模式的事件中心模块类似于前端开发中的 EventBus。作用范围仅适用于同一 UIAbility 内主线程各组件间的通信如页面间的数据同步、状态回传。核心机制每个 UIAbility 的 Context 拥有独立的 EventHub 实例不同 UIAbility 的 EventHub 互不共享。API 极简仅暴露on订阅、off取消订阅和emit触发事件三个方法通过字符串类型的事件名TAG进行匹配无需权限管控极其轻量。二、 跨线程的异步事件处理Emitter当应用涉及多线程并发如 Worker、TaskPool时EventHub 无法跨线程通信此时需要使用 Emitter。作用范围用于同一进程内相同线程或不同线程间的事件处理。核心机制Emitter 内部维护了一个事件队列发布事件后任务队列会串行执行并调用订阅者的回调方法事件是异步执行的。优先级控制支持为事件设置优先级如 IMMEDIATE、HIGH、LOW、IDLE系统会根据优先级顺序分发事件。三、 跨应用与系统级事件CommonEventManager对于需要跨进程或响应系统状态的场景需要使用 CommonEventManager。作用范围用于订阅系统级公共事件如低电量、网络变化、账号登出或实现跨应用、跨进程的自定义事件通信。权限管控支持应用间的安全通信部分系统事件需要申请相应权限才能订阅。兼容性同时兼容 FA 模型与 Stage 模型。四、 EventHub 实战同 Ability 内的跨组件状态同步场景在复杂的页面层级中如首页卡片、侧边栏、弹窗组件之间没有直接的父子关系。通过 EventHub 实现一处修改全局多处实时响应。import { common } from kit.AbilityKit; // 1. 触发事件的组件如弹窗内的开关 Component struct PopupComponent { private eventHub: common.EventHub getContext(this).eventHub; build() { Button(切换状态).onClick(() { // 广播事件并携带业务数据 this.eventHub.emit(TOGGLE_STATUS, { status: true }); }) } } // 2. 接收事件的组件如首页卡片 Component struct HomeCard { State status: boolean false; private eventHub: common.EventHub getContext(this).eventHub; aboutToAppear(): void { // 订阅事件 this.eventHub.on(TOGGLE_STATUS, (data: Recordstring, Object) { this.status data[status] as boolean; }); } aboutToDisappear(): void { // 【关键】组件销毁时务必取消订阅防止内存泄漏 this.eventHub.off(TOGGLE_STATUS); } }五、 Emitter 实战Worker 子线程与 UI 线程的异步通信场景在后台 Worker 线程中进行耗时的数据计算或文件读取处理完成后将结果安全地传递给 UI 线程进行渲染。import { emitter } from kit.BasicServicesKit; // 1. UI 线程订阅子线程消息 Component struct MainPage { State result: number 0; aboutToAppear(): void { // 订阅事件支持优先级设置 const event: emitter.InnerEvent { eventId: 1001, priority: emitter.EventPriority.HIGH }; emitter.on(event, (eventData: emitter.EventData) { this.result eventData.data?.[value] as number; }); } aboutToDisappear(): void { emitter.off(1001); } } // 2. Worker 线程发布消息在 worker.ts 中 import { emitter } from kit.BasicServicesKit; const event: emitter.InnerEvent { eventId: 1001 }; const eventData: emitter.EventData { data: { value: 99 } }; emitter.emit(event, eventData); // 异步投递到 UI 线程队列六、CommonEventManager 实战监听系统级公共事件场景应用需要实时感知设备的网络状态变化或电量情况以便在弱网或低电量下动态调整业务策略如暂停高清视频加载。import { commonEventManager } from kit.BasicServicesKit; export class SystemEventMonitor { private subscriber?: commonEventManager.CommonEventSubscriber; // 订阅系统网络变化事件 async subscribeNetworkChange(callback: (isConnected: boolean) void): Promisevoid { const subscribeInfo: commonEventManager.CommonEventSubscribeInfo { events: [usual.event.NETWORK_STATE_UPDATE] // 系统预定义的网络事件 }; this.subscriber await commonEventManager.createSubscriber(subscribeInfo); commonEventManager.subscribe(this.subscriber, (err, data) { if (err) return; // 解析网络状态并回调 const isConnected data.data CONNECTED; callback(isConnected); }); } // 释放订阅资源 unsubscribe(): void { if (this.subscriber) { commonEventManager.unsubscribe(this.subscriber); } } }七、 EventHub 进阶UIAbility 与 UI 界面的双向数据同步场景在鸿蒙的 Stage 模型中UIAbility 与 UI 界面是分离的。当 Ability 层接收到外部消息如系统通知、跨进程通信时需要将其精准推送给正在显示的 UI 界面进行渲染。// 1. 在 UIAbility 中订阅事件并转发给 UI import { UIAbility, Want } from kit.AbilityKit; export default class EntryAbility extends UIAbility { onCreate(want: Want): void { // Ability 监听外部事件 this.context.eventHub.on(externalDataSync, (data: string) { // 将数据通过同一个 eventHub 转发给 UI 层 this.context.eventHub.emit(updateUI, data); }); } } // 2. 在 UI 界面中接收并更新状态 Entry Component struct SyncPage { State displayText: string 等待同步...; aboutToAppear(): void { const context getContext(this) as common.UIAbilityContext; context.eventHub.on(updateUI, (data: string) { this.displayText data; }); } aboutToDisappear(): void { const context getContext(this) as common.UIAbilityContext; context.eventHub.off(updateUI); } build() { Text(this.displayText).fontSize(20) } }八、 Emitter 进阶基于 Sendable 的跨线程零拷贝对象传递场景在多线程并发场景下如果通过 Emitter 传递庞大的复杂对象常规的序列化/反序列化会带来严重的性能开销。结合鸿蒙的Sendable机制可实现跨线程的高效数据流转。import { emitter } from kit.BasicServicesKit; // 1. 定义支持跨线程共享的 Sendable 对象 Sendable class SensorData { public x: number 0; public y: number 0; constructor(x: number, y: number) { this.x x; this.y y; } } // 2. 封装 Emitter 发送逻辑 class SensorEmitter { static emitSensorData(data: SensorData): void { const innerEvent: emitter.InnerEvent { eventId: 2001 }; const eventData: emitter.EventData { data: data }; emitter.emit(innerEvent, eventData); } } // 3. 在 UI 线程接收无需 JSON 解析直接获取对象属性 emitter.on({ eventId: 2001 }, (eventData: emitter.EventData) { const sensor eventData.data as SensorData; console.info(接收到传感器数据: X${sensor.x}, Y${sensor.y}); });九、CommonEventManager 进阶跨应用定向通信与防泄漏场景在电商或物联网生态中主应用需要向特定的桌面卡片Widget或子应用发送“订单支付成功”事件。为防止设备上的无关应用监听到该广播导致信息泄漏必须使用包名定向发布。import { commonEventManager } from kit.BasicServicesKit; // 发送方限定接收方的 bundleName export function publishOrderPaid(targetBundleName: string, orderId: string): void { const publishData: commonEventManager.CommonEventPublishData { bundleName: targetBundleName, // 核心仅此包名的应用可收到 data: JSON.stringify({ orderId, ts: Date.now() }) }; commonEventManager.publish(com.shop.ORDER_PAID, publishData, (err) { if (err) { console.error(发布订单事件失败: ${err.message}); } }); } // 接收方普通订阅即可系统会自动校验 bundleName 是否匹配 const subscribeInfo: commonEventManager.CommonEventSubscribeInfo { events: [com.shop.ORDER_PAID] }; const subscriber await commonEventManager.createSubscriber(subscribeInfo); commonEventManager.subscribe(subscriber, (err, data) { if (!err data) { const payload JSON.parse(data.data!); console.log(卡片收到订单: ${payload.orderId}); } });在落地事件总线机制时开发者需特别注意以下工程陷阱严格的生命周期绑定无论是 EventHub 还是 Emitter都必须在组件的aboutToDisappear或页面的onDestroy中调用off取消订阅。未取消的订阅会导致组件实例无法被 GC 回收引发严重的内存泄漏。跨线程数据序列化限制使用 Emitter 跨线程传递数据时传递的对象必须是可序列化的普通对象严禁传递包含 UI 装饰器如State、Link的复杂类型或闭包函数否则会导致运行时崩溃。避免事件风暴与死循环在事件回调中再次触发同类事件极易导致无限递归。对于高频触发的事件如滚动、滑动必须在业务层加入防抖Debounce或节流Throttle机制。权限与隐私合规使用 CommonEventManager 订阅系统级事件时务必在module.json5中检查并声明所需的系统权限如ohos.permission.GET_NETWORK_INFO否则订阅将静默失败。