引言
在移动应用架构中,组件间的通信始终是一个核心命题。HarmonyOS 的 Stage 模型将 UI 页面与后台 Ability 隔离在不同生命周期中,客观上要求一套可靠的跨组件、跨进程通信机制。Android 开发者熟悉 BroadcastReceiver,iOS 开发者熟悉 NotificationCenter——而 HarmonyOS NEXT 的答案是@ohos.commonEventManager,一套完整的公共事件(Common Event)发布/订阅系统。
与@ohos.events.emitter的进程内事件总线不同,commonEventManager工作在系统服务层——事件经由系统 Common Event Service 路由,可以实现跨应用、跨进程的事件传递。系统本身也通过这套机制广播电池状态、网络连接、时区变化等底层状态事件(即Support枚举中定义的各类系统公共事件)。
本文将构建一个事件总线实验室Demo,从零开始演示自定义事件的完整生命周期:创建订阅者(Subscriber)、注册事件监听、发布带数据的公共事件、处理有序事件(Ordered Event),并以实时日志追踪每一步的状态流转。
读完本文,你将掌握:
- 订阅者创建:
createSubscriberSync()— 同步构建 CommonEventSubscriber - 事件订阅:
subscribe()— 注册带事件匹配规则的持久监听 - 事件发布:
publish()— 发布基本事件 / 带 code + data 的结构化事件 - 有序事件:
isOrdered— 有序事件的发布与结果码处理 - 数据流:
CommonEventPublishData→CommonEventData— 发布端到接收端的数据传递 - 系统事件:
Support枚举 — 30+ 系统公共事件的分类与用途
环境与权限
@ohos.commonEventManager属于BasicServicesKit工具包,自 API 9 开始提供。导入方式:
import{commonEventManager}from'@kit.BasicServicesKit';重要发现:发布和订阅自定义事件不需要任何权限。只有以下操作需要额外权限:
| 操作 | 所需权限 | 级别 |
|---|---|---|
| 发布自定义事件 | 无需权限 | — |
| 订阅自定义事件 | 无需权限 | — |
| 发布粘性事件 (isSticky) | ohos.permission.COMMONEVENT_STICKY | system_grant |
| 订阅部分系统事件 | 视具体事件而定 | 部分需 system 权限 |
对于大多数应用场景(应用内跨组件通信、自定义事件总线),commonEventManager是零权限开销的。本文 Demo 也无需任何权限声明即可运行。
一、核心数据结构
在深入 API 使用之前,先理解四个关键接口。
1.1 CommonEventSubscribeInfo — 订阅配置
interfaceCommonEventSubscribeInfo{events:Array<string>;// 要订阅的事件名列表(支持多事件匹配)publisherPermission?:string;// 发布者所需权限(过滤来源)publisherDeviceId?:string;// 发布者设备 ID(分布式场景)userId?:number;// 发布者用户 IDpriority?:number;// 订阅者优先级(-100 ~ +1000)publisherBundleName?:string;// 发布者 Bundle Name(精确定向)}events是唯一必填字段,支持字符串数组——一个 Subscriber 可以同时匹配多个事件名。priority在有序事件中至关重要:高优先级的订阅者先收到事件,并有机会通过setCode()/setData()影响下游订阅者。
匹配规则:
- 事件名精确匹配时,订阅者收到事件
- 若指定了
publisherBundleName,仅接收该 Bundle 发布的事件 - 若指定了
publisherPermission,仅接收声明了该权限的发布者的事件
1.2 CommonEventPublishData — 发布数据
interfaceCommonEventPublishData{bundleName?:string;// 限定接收者的 Bundle Namecode?:number;// 结果码(默认 0)data?:string;// 字符串数据(最大 64KB)subscriberPermissions?:Array<string>;// 接收者所需权限isOrdered?:boolean;// 是否有序事件(默认 false)isSticky?:boolean;// 是否粘性事件(需 COMMONEVENT_STICKY 权限)parameters?:{[key:string]:any};// 附加键值对}code和data是发布者向订阅者传递信息的两个主通道。code通常用于状态码(类似 HTTP 状态码),data用于承载字符串数据。parameters提供了更灵活的键值对扩展——可以传递任意 JSON 可序列化的结构。
注意:data字段限制 64KB,如果需要传输更大的数据,应通过文件路径或 URI 传递。
1.3 CommonEventData — 接收数据
interfaceCommonEventData{event:string;// 事件名称bundleName?:string;// 发布者的 Bundle Namecode?:number;// 发布时传入的 codedata?:string;// 发布时传入的 dataparameters?:{[key:string]:any};// 发布时传入的 parameters}订阅者在回调中收到CommonEventData,它是CommonEventPublishData的"镜像"——发布时的code、data、parameters原封不动传递到接收端。event字段告诉订阅者当前触发的是哪个事件(当订阅了多个事件时尤为重要)。
1.4 CommonEventSubscriber — 订阅者实例
interfaceCommonEventSubscriber{getCode(callback:AsyncCallback<number>):void;getCodeSync():number;// 同步获取当前结果码setCode(code:number):Promise<void>;setCodeSync(code:number):void;// 同步设置结果码getData(callback:AsyncCallback<string>):void;getDataSync():string;// 同步获取当前结果数据setData(data:string):Promise<void>;setCodeAndData(code:number,data:string):Promise<void>;isOrderedCommonEventSync():boolean;// 是否有序事件isStickyCommonEventSync():boolean;// 是否粘性事件abortCommonEventSync():void;// 中断有序事件传递finishCommonEvent():Promise<void>;// 完成有序事件处理getSubscribeInfoSync():CommonEventSubscribeInfo;// 获取订阅信息}CommonEventSubscriber不仅是订阅句柄,在有序事件中,它还是事件处理的控制点。通过setCode()/setData(),订阅者可以修改传递给下一个订阅者的结果;通过abortCommonEventSync(),可以终止事件继续向后传递。
二、完整生命周期
一个完整的自定义事件流包含五个步骤:
2.1 创建订阅者
import{commonEventManager}from'@kit.BasicServicesKit';// 定义订阅信息letsubscribeInfo:commonEventManager.CommonEventSubscribeInfo={events:['com.example.MY_EVENT']};// 同步创建 Subscriber(API 10+)letsubscriber=commonEventManager.createSubscriberSync(subscribeInfo);createSubscriberSync()是 API 10 引入的同步方法,直接返回CommonEventSubscriber实例。如果你的项目最低 API 版本是 9,可以使用 Promise 版本:
letsubscriber=awaitcommonEventManager.createSubscriber(subscribeInfo);CommonEventSubscribeInfo的events数组支持多个事件名——一个 Subscriber 可以同时监听多个不同的事件。这比 Android BroadcastReceiver 的 IntentFilter 设计更简洁。
2.2 订阅事件
commonEventManager.subscribe(subscriber,(err:Error,data:commonEventManager.CommonEventData)=>{if(err){console.error('订阅失败: '+JSON.stringify(err));return;}console.log('收到事件: '+data.event);console.log(' code: '+(data.code||0));console.log(' data: '+(data.data||''));console.log(' bundleName: '+(data.bundleName||'(本应用)'));});subscribe()注册一个异步回调,当匹配的事件被发布时,系统会调用该回调并传入CommonEventData。回调在主线程执行——如果处理逻辑较重,应考虑异步化。
2.3 发布事件
// 基本发布:仅事件名commonEventManager.publish('com.example.MY_EVENT',(err:Error)=>{if(err){console.error('发布失败: '+JSON.stringify(err));}else{console.log('发布成功');}});// 带数据发布letpublishData:commonEventManager.CommonEventPublishData={code:200,data:'{"msg":"Hello CommonEvent!"}',isOrdered:false};commonEventManager.publish('com.example.MY_EVENT',publishData,(err:Error)=>{// ...});publish()是异步的——事件被提交到系统 Common Event Service 后,由 Service 异步分发给所有匹配的订阅者。发布者不等待订阅者处理完毕。
2.4 取消订阅
commonEventManager.unsubscribe(subscriber);unsubscribe()传入 Subscriber 实例,系统会移除该订阅者的所有事件监听。未取消的订阅可能导致内存泄漏——建议在页面aboutToDisappear()生命周期中清理。
2.5 完整示例:计数器事件广播
import{commonEventManager}from'@kit.BasicServicesKit';// 发布端letpublishInfo:commonEventManager.CommonEventPublishData={code:1,data:'increment'};commonEventManager.publish('com.example.COUNTER',publishInfo,(err:Error)=>{console.log('计数器事件已发布');});// 接收端letinfo:commonEventManager.CommonEventSubscribeInfo={events:['com.example.COUNTER']};letsub=commonEventManager.createSubscriberSync(info);commonEventManager.subscribe(sub,(err:Error,data:commonEventManager.CommonEventData)=>{if(data.code===1&&data.data==='increment'){// 执行计数器加一操作}});三、有序事件(Ordered Event)
有序事件是commonEventManager的独特设计之一。当isOrdered设为true时,事件按订阅者优先级串行传递——高优先级订阅者先收到,可以修改结果码/数据,甚至可以中断传递链。
3.1 工作原理
发布者 publish(isOrdered=true) │ ▼ 订阅者 A (priority=1000) ← 最先收到 │ setCodeAndData(1, 'processed_by_A') ▼ 订阅者 B (priority=500) ← 收到 A 修改后的数据 │ getCodeSync() → 1 │ getDataSync() → 'processed_by_A' │ abortCommonEventSync() ← 中断传递 ▼ 订阅者 C (priority=100) ← 不会收到(已被中断) │ ▼ 发布者回调 → publish 完成3.2 代码演示
// 发布者发布有序事件letpublishData:commonEventManager.CommonEventPublishData={code:0,data:'original',isOrdered:true// 关键:标记为有序事件};commonEventManager.publish('com.example.ORDERED',publishData,(err:Error)=>{console.log('有序事件发布完成');});// 高优先级订阅者也叫"A"letsubA=commonEventManager.createSubscriberSync({events:['com.example.ORDERED'],priority:1000});commonEventManager.subscribe(subA,(err:Error,data:commonEventManager.CommonEventData)=>{// 修改结果,传递给下游subA.setCodeAndDataSync(1,'processed_by_A');subA.finishCommonEvent();// 必须调用,否则事件链卡住});// 中等优先级订阅者"B"letsubB=commonEventManager.createSubscriberSync({events:['com.example.ORDERED'],priority:500});commonEventManager.subscribe(subB,(err:Error,data:commonEventManager.CommonEventData)=>{letcode=subB.getCodeSync();// → 1(由 A 设置)letd=subB.getDataSync();// → 'processed_by_A'// 中断传递,C 不会收到此事件subB.abortCommonEventSync();subB.finishCommonEvent();});// 低优先级订阅者"C"letsubC=commonEventManager.createSubscriberSync({events:['com.example.ORDERED'],priority:100});commonEventManager.subscribe(subC,(err:Error,data:commonEventManager.CommonEventData)=>{// 当 A 或 B 调用了 abortCommonEventSync() 后,C 不会执行到这里});关键规则:
finishCommonEvent()是必需的——不调用会导致事件链永久阻塞,后续订阅者永远等不到abortCommonEventSync()中断传递,之后的订阅者不会收到事件- 优先级值域为 -100 ~ +1000,值越大越先收到
四、系统公共事件(Support 枚举)
Support枚举定义了 30+ 系统公共事件,覆盖了设备管理的数个关键领域:
| 分类 | 事件名 | 触发时机 |
|---|---|---|
| 设备状态 | usual.event.BOOT_COMPLETED | 设备启动完成 |
| 设备状态 | usual.event.SHUTDOWN | 设备关机 |
| 电池 | usual.event.BATTERY_CHANGED | 电池状态变化 |
| 电池 | usual.event.BATTERY_LOW | 低电量 |
| 电池 | usual.event.BATTERY_OKAY | 电量恢复正常 |
| 电源 | usual.event.POWER_CONNECTED | 电源连接 |
| 电源 | usual.event.POWER_DISCONNECTED | 电源断开 |
| 屏幕 | usual.event.SCREEN_ON | 屏幕点亮 |
| 屏幕 | usual.event.SCREEN_OFF | 屏幕关闭 |
| 时间 | usual.event.TIME_TICK | 每分钟计时 |
| 时间 | usual.event.TIME_CHANGED | 系统时间被修改 |
| 时间 | usual.event.TIMEZONE_CHANGED | 时区变更 |
| 温控 | usual.event.THERMAL_LEVEL_CHANGED | 温度等级变化 |
| 应用 | usual.event.PACKAGE_ADDED | 新应用安装(系统应用) |
| 应用 | usual.event.PACKAGE_REPLACED | 应用更新(系统应用) |
注意:标有"系统应用"的事件需要 system 权限或系统应用身份才能订阅。普通应用可以自由订阅电池、电源、屏幕、时间、时区等无需权限的事件——这些是打造系统感知型应用的绝佳入口。
以下示例演示订阅电池变化事件:
letinfo:commonEventManager.CommonEventSubscribeInfo={events:[commonEventManager.Support.COMMON_EVENT_BATTERY_CHANGED,commonEventManager.Support.COMMON_EVENT_POWER_CONNECTED]};letsub=commonEventManager.createSubscriberSync(info);commonEventManager.subscribe(sub,(err:Error,data:commonEventManager.CommonEventData)=>{if(data.event===commonEventManager.Support.COMMON_EVENT_BATTERY_CHANGED){console.log('电池状态变化: code='+data.code);}elseif(data.event===commonEventManager.Support.COMMON_EVENT_POWER_CONNECTED){console.log('电源已连接');}});五、实战 Demo:事件总线实验室
页面结构
事件总线实验室 ├── 状态栏 — 订阅状态 + 已收到事件计数 ├── 事件订阅配置 │ ├── 事件名输入框 │ └── 订阅 / 取消订阅 按钮 ├── 事件发布面板 │ ├── 事件名输入框 │ ├── Code 输入框 │ ├── Data 输入框 │ ├── 有序事件 Toggle │ └── 发布按钮 ├── 事件日志 — 最近 50 条(含时间戳) ├── 系统公共事件参考 — 10 个常用事件 └── 核心 API 参考 — 10 个关键 API4 个交互点
- 订阅自定义事件— 输入事件名,点击"订阅"创建 Subscriber 并注册回调,状态实时反馈
- 发布事件— 编辑事件名、code、data,勾选有序/无序,点击"发布"触发;日志立即显示发布成功和接收确认
- 有序事件切换— 勾选有序事件 Toggle 后发布,Demo 演示发布端如何通过
isOrdered参数控制事件传递模式 - 实时事件日志— 所有操作(订阅/取消/发布/接收/异常)均以带时间戳的彩色日志呈现,最近 50 条
核心代码位置
完整代码在dev/entry/src/main/ets/pages/EventBusLabPage.ets(约 330 行),路由已注册为pages/EventBusLabPage。
六、commonEventManager vs events.emitter
HarmonyOS 提供了两个事件机制,理解它们的差异至关重要:
| 维度 | commonEventManager | @ohos.events.emitter |
|---|---|---|
| 通信范围 | 跨进程(系统级) | 进程内 |
| 底层机制 | 系统 Common Event Service | 内存事件表 |
| 权限需求 | 无(自定义事件) | 无 |
| 有序事件 | 支持(priority + abort) | 支持(EventPriority) |
| 粘性事件 | 支持(需权限) | 不支持 |
| 系统事件 | Support 枚举(30+) | 不支持 |
| 适用场景 | 跨应用通信、系统监听 | 页面/组件解耦 |
简而言之:跨进程通信用 commonEventManager,进程内解耦用 emitter。本文 Demo 虽然只在单应用内收发,但展示了完整的commonEventManagerAPI 栈,便于扩展到跨应用场景。
七、与 Android BroadcastReceiver 的对比
| 维度 | Android BroadcastReceiver | HarmonyOS commonEventManager |
|---|---|---|
| 注册方式 | 动态注册 + 静态注册(Manifest) | createSubscriberSync + subscribe |
| 传递数据 | Intent.putExtra() | code + data + parameters |
| 有序广播 | sendOrderedBroadcast() | isOrdered + priority + abort |
| 粘性广播 | sendStickyBroadcast() (Deprecated) | isSticky(需权限) |
| 系统广播 | Intent.ACTION_BATTERY_CHANGED 等 | Support 枚举 |
| 权限控制 | 自定义 permission | publisherPermission / subscriberPermissions |
| 安全模型 | 较松散(静态注册易被唤醒) | 严格(需要显式声明接收者权限) |
HarmonyOS 的设计更加显式化:发布者必须指定哪些权限的订阅者可以接收事件,订阅者也可以筛选发布者——双向权限校验带来更强的安全保证。
八、最佳实践与注意事项
8.1 生命周期管理
aboutToDisappear():void{if(this.subscriber!==null){commonEventManager.unsubscribe(this.subscriber);this.subscriber=null;}}页面销毁前务必取消订阅。未清理的 Subscriber 会导致内存泄漏和无效回调。
8.2 主线程回调
subscribe()的回调在主线程执行。如果回调中包含耗时操作(如数据库写入、网络请求),应使用@ohos.worker将逻辑切换到子线程。
8.3 有序事件的 finishCommonEvent
在有序事件中,每个订阅者必须调用finishCommonEvent(),否则事件链会永久阻塞——系统在等待当前订阅者完成处理。这是新手最容易踩的坑。
8.4 事件命名建议
推荐使用反向域名命名法,如com.example.module.ACTION_NAME,避免与系统事件命名冲突。系统事件统一使用usual.event.*前缀。
8.5 data 大小限制
CommonEventPublishData.data最大 64KB。超出此限制会导致发布失败。大数据传输应使用文件路径或分布式数据对象。
九、API 总结表
| API | 返回值 | 说明 |
|---|---|---|
commonEventManager.createSubscriberSync(info) | CommonEventSubscriber | 同步创建订阅者(API 10+) |
commonEventManager.createSubscriber(info) | Promise<CommonEventSubscriber> | 异步创建订阅者(API 9+) |
commonEventManager.subscribe(sub, cb) | void | 订阅事件 |
commonEventManager.unsubscribe(sub) | void | 取消订阅 |
commonEventManager.publish(event, cb) | void | 发布简单事件 |
commonEventManager.publish(event, opts, cb) | void | 发布带数据的事件 |
Subscriber.getCodeSync() | number | 同步获取结果码 |
Subscriber.setCodeAndDataSync(code, data) | void | 同步设置结果码和数据 |
Subscriber.finishCommonEvent() | Promise<void> | 完成有序事件处理 |
Subscriber.abortCommonEventSync() | void | 中断有序事件传递 |
十、总结
@ohos.commonEventManager是 HarmonyOS NEXT 的进程间通信主干,提供了一套完整的发布/订阅系统:
- 零权限开销:自定义事件的发布和订阅无需任何权限声明
- 丰富的传递数据结构:
code+data+parameters三层载荷通道 - 有序事件链:基于优先级的串行传递,支持结果码修改和传递中断
- 系统事件感知:
Support枚举提供 30+ 系统公共事件,覆盖电池、电源、屏幕、时间、温控等场景 - 安全模型:双向权限校验(发布方限定接收者,订阅方筛选发布者)
这个模块与@ohos.events.emitter(进程内事件)和@ohos.net.http(HTTP 通信)共同构成了 HarmonyOS 应用通信的完整层级——从进程内解耦,到跨组件广播,再到网络请求,开发者可以根据通信场景精准选择对应工具。