一、为什么需要公共事件?

开发 OpenHarmony 应用时,组件之间经常需要传递"状态变化"消息,典型场景:

  1. 跨页面通知:A 页面设备状态变了,B 页面需要同步刷新,但两个页面没有直接的引用关系;
  2. 跨 Ability/跨应用广播:一个模块采集到的数据,需要分发给多个订阅方(如测试框架收集用例结果);
  3. 监听系统事件:蓝牙开关变化、网络连接变化、屏幕亮灭等系统级事件。

如果全部用全局变量、回调注册表或 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 或文件
Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐