OpenHarmony 公共事件(CommonEvent)开发实践
·
一、为什么需要公共事件?
开发 OpenHarmony 应用时,组件之间经常需要传递"状态变化"消息,典型场景:
- 跨页面通知:A 页面设备状态变了,B 页面需要同步刷新,但两个页面没有直接的引用关系;
- 跨 Ability/跨应用广播:一个模块采集到的数据,需要分发给多个订阅方(如测试框架收集用例结果);
- 监听系统事件:蓝牙开关变化、网络连接变化、屏幕亮灭等系统级事件。
如果全部用全局变量、回调注册表或 EventHub 硬编码,会出现耦合严重、生命周期管理混乱、跨应用无法通信的问题。公共事件(CommonEvent)机制就是官方提供的发布-订阅解耦方案:发布方不需要知道谁在听,订阅方按事件名匹配即可。
二、哪些场景适合用?
| 场景 | 是否适合 | 说明 |
|---|---|---|
| 同 Ability 内页面间通信 | 不推荐 | 优先用状态管理或 EventHub,开销更小 |
| 跨 UIAbility / 跨模块广播状态变化 | 适合 | 如设备数据变化、连接状态变化广播 |
| 跨应用通信(无 IPC 连接时) | 适合 | 系统公共事件或自定义事件广播 |
| 监听系统事件(蓝牙/网络/电池等) | 适合 | 使用系统预定义事件名,免注册 |
| 高频、大数据量传输 | 不推荐 | 公共事件有发送频率与大小限制,大数据走 RPC 或文件 |
三、核心机制
3.1 发布-订阅模型
发布方 publish(event, options)
│
▼
┌──────────────────┐
│ 系统公共事件服务 │ 事件名匹配 + 权限校验
└──────────────────┘
│
▼
订阅方 createSubscriber + subscribe → onReceive 回调
- 发布方调用 publish 发送事件,不需要知道订阅方是谁;
- 订阅方创建订阅者(Subscriber)并声明感兴趣的事件名(可多个),系统按事件名分发;
- 事件通过系统进程中转,天然支持跨进程/跨应用。
3.2 两种订阅方式
| 方式 | 说明 | 适用 |
|---|---|---|
| 动态订阅 | 运行时 createSubscriber + subscribe,随组件生命周期注册/注销 | 大多数业务场景(推荐) |
| 静态订阅 | 通过 ExtensionAbility 常驻订阅(仅系统应用,需 COMMON_EVENT_STATIC_SUBSCRIPTION 权限) | 系统级监听 |
3.3 事件类型
- 普通事件:发布后即时分发,无序;
- 有序事件:options.isOrdered 为 true,订阅者按订阅顺序依次收到,可中断后续分发;
- 粘性事件:options.isSticky 为 true,事件持久保留,后订阅者也能立刻收到最新一条。
四、核心 API
| 接口 | 说明 |
|---|---|
| commonEventManager.createSubscriber(info, callback) | 创建订阅者(info.events 声明订阅的事件名列表) |
| commonEventManager.subscribe(subscriber, callback) | 开始订阅,事件到达时回调 onReceive |
| commonEventManager.unsubscribe(subscriber, callback) | 取消订阅 |
| commonEventManager.publish(event, options, callback) | 发布事件(options 可带 code、data、isOrdered、isSticky) |
| commonEventManager.off(event) | 移除系统事件监听(系统事件场景) |
五、示例
5.1 场景说明
- 业务模块采集到"设备状态变化"后发布公共事件;
- 页面订阅该事件实时刷新 UI,页面销毁时取消订阅,避免泄漏。
5.2 发布公共事件
import commonEventManager from '@ohos.commonEventManager';
const EVENT_DEVICE_STATE_CHANGED = 'usual.event.DEVICE_STATE_CHANGED'; // 建议使用 usual.event 前缀
export function publishDeviceStateChanged(state: Record<string, Object>): void {
let options: commonEventManager.CommonEventPublishData = {
code: 0,
data: JSON.stringify(state), // 业务数据放 data 字段
isOrdered: false,
isSticky: false
};
commonEventManager.publish(EVENT_DEVICE_STATE_CHANGED, options, (err) => {
if (err) {
console.error('publish failed, code is ' + err.code + ', message is ' + err.message);
} else {
console.info('publish success');
}
});
}
5.3 订阅公共事件(页面侧)
import commonEventManager from '@ohos.commonEventManager';
import { BusinessError } from '@kit.BasicServicesKit';
export class DeviceEventSubscriber {
private subscriber: commonEventManager.CommonEventSubscriber | null = null;
/** 订阅事件:传入回调,事件到达时触发 */
public async subscribe(onEvent: (data: string) => void): Promise<void> {
let subscribeInfo: commonEventManager.CommonEventSubscribeInfo = {
events: [EVENT_DEVICE_STATE_CHANGED] // 声明订阅的事件名
};
try {
// 1. 创建订阅者
this.subscriber = await commonEventManager.createSubscriber(subscribeInfo);
// 2. 开始订阅
await commonEventManager.subscribe(this.subscriber, (err, data) => {
if (err) {
console.error('receive failed, code is ' + err.code);
return;
}
// data.event 为事件名,data.data 为发布时携带的数据
onEvent(data.data ?? '');
});
} catch (e) {
let err = e as BusinessError;
console.error('subscribe failed, code is ' + err.code + ', message is ' + err.message);
}
}
/** 取消订阅:页面销毁时必须调用,防止泄漏 */
public async unsubscribe(): Promise<void> {
if (this.subscriber) {
await commonEventManager.unsubscribe(this.subscriber);
this.subscriber = null;
}
}
}
5.4 在页面生命周期中使用
@Entry
@Component
struct DevicePage {
private eventSubscriber: DeviceEventSubscriber = new DeviceEventSubscriber();
@State deviceState: string = '';
async aboutToAppear() {
// 页面出现时注册订阅
await this.eventSubscriber.subscribe((data: string) => {
this.deviceState = data; // 事件到达刷新 UI
});
}
aboutToDisappear() {
// 页面销毁时注销订阅(关键:避免订阅泄漏)
this.eventSubscriber.unsubscribe();
}
build() {
Text('设备状态: ' + this.deviceState);
}
}
5.5 订阅系统公共事件(如蓝牙开关变化)
import { bluetoothManager } from '@kit.ConnectivityKit';
import commonEventManager from '@ohos.commonEventManager';
export function watchBluetoothState(onChanged: (state: number) => void): void {
// 订阅系统蓝牙状态变化事件(系统事件免自定义发布方)
let subscribeInfo: commonEventManager.CommonEventSubscribeInfo = {
events: [commonEventManager.CommonEventSupport.COMMON_EVENT_BLUETOOTH_STATE_CHANGED]
};
commonEventManager.createSubscriber(subscribeInfo, (err, subscriber) => {
if (err) {
console.error('createSubscriber failed');
return;
}
commonEventManager.subscribe(subscriber, (err, data) => {
// 解析系统携带的状态参数
let state = (data.parameters as Record<string, number>)['state'] ?? -1;
onChanged(state);
});
});
}
六、常见问题与踩坑总结
| 现象 | 原因 | 解决方案 |
|---|---|---|
| 订阅后收不到事件 | 订阅的事件名与发布的事件名不一致(含大小写/前缀);先订阅再发布顺序错 | 事件名统一定义为常量,两端引用同一常量;确认订阅先于发布完成 |
| 页面销毁后仍收到回调 | 未调用 unsubscribe 注销订阅 | 在 aboutToDisappear 中注销订阅者 |
| 重复创建订阅者导致回调多次 | 每次订阅都新建 Subscriber 未复用 | 复用同一 Subscriber;重复订阅前先注销旧的 |
| 后进入页面收不到"最新状态" | 普通事件不保留历史,后订阅者收不到已发布的事件 | 需要时使用粘性事件(isSticky: true) |
| 发布失败错误码提示权限/参数异常 | options 未传或 events 为空;自定义事件名不合规 | 自定义事件建议 usual.event 前缀;检查 CommonEventPublishData 字段 |
| 跨应用收不到事件 | 订阅/发布双方未声明相同事件名,或事件被系统安全策略拦截 | 确认双方事件名一致;系统事件用官方预定义常量 |
| 高频发布导致消息丢失 | 超过系统发布频率限制 | 控制发布频率,必要时合并为批量事件 |
| 大数据通过事件传递失败 | 超过公共事件数据大小限制 | 事件只传轻量标识(如状态码、ID),大 payload 走 RPC 或文件 |
更多推荐
所有评论(0)