HarmonyOS 公共事件机制介绍以及多进程之间的通信
CES(Common Event Service,公共事件服务)为应用程序提供订阅、发布、退订公共事件的能力
1. 公共事件的介绍
1.1.1公共事件的分类:公共事件从系统的角度可以分为系统公共事件和自定义公共事件
-
系统公共事件:CES内部定义的公共事件,当前仅支持系统应用和系统服务发布,例如HAP安装、更新、卸载等公共事件。目前支持的系统公共事件请参考系统公共事件列表
-
自定义的公共事件:应用定义的公共事件,可用于实现跨进程的事件通信能力(重点:可以死实现跨进程通信)
1.1.2 公共事件的发送方式
-
无序公共事件:CES在转发公共事件时,不考虑订阅者是否接收到该事件,也不保证订阅者接收到该事件的顺序与其订阅顺序一致。打个比方就好像群发短信,广播式发送,一次性发给所有订阅者,不保证顺序,谁先收到看运气,不保证送达,可能都有接收失败的情况。比如老师给全班群发短信,同学A手机信号好,收到信息的时间是13:05,而B同学的手机比较卡顿,收到信息的时间为13:07。
-
代码示例
// 发布无序事件 CommonEventData event = new CommonEventData("MSG_UPDATE"); CommonEventManager.publishCommonEvent(event);
-
适用场景
- 非关键性的通知(如:APP内容更新提醒)
- 不需要反馈的广播(如定时任务的触发)
-
-
有序公共事件:CES在转发公共事件时,根据订阅者设置的优先级等级,优先将公共事件发送给优先级较高的订阅者,等待其成功接收该公共事件之后再将事件发送给优先级较低的订阅者。如果有多个订阅者具有相同的优先级,则他们将随机接收到公共事件。打个比方就好像银行的呼叫系统,严格按照优先级,高优先级先处理,前一个处理完才传下一个,链式传递,任一环节可终止传递,就好比商场的vip服务,钻石客户优先办理。
-
代码示例
// 订阅时设置优先级 CommonEventSubscribeInfo info = new CommonEventSubscribeInfo(); info.setPriority(9); // 最高优先级CommonEventSubscriber subscriber = new CommonEventSubscriber(info) {@Overridepublic void onReceiveEvent(CommonEventData event) {// 处理完成后必须调用此方法才会传递给下一级!finishCommonEvent();} };
-
适用场景
- 需要审核流程的操作(比如付款确认链)
- 敏感操作验证(多层安全校验)
-
-
粘性公共事件:能够让订阅者收到在订阅前已经发送的公共事件就是粘性公共事件。普通的公共事件只能在订阅后发送才能收到,而粘性公共事件的特殊性就是可以先发送后订阅,同时也支持先订阅后发送。发送粘性事件必须是系统应用或系统服务,粘性事件发送后会一直存在系统中,且发送者需要申请ohos.permission.COMMONEVENT_STICKY权限,配置方式请参见声明权限。打个比方来说就像小区公告栏贴出的停水通知,居民A当天看到,就可以马上储水,居民B三天后搬来,看到历史通知,只有物业(系统应用)有权粘贴
- 代码
// 发布粘性事件(需系统权限) const commonEvent = {name: "CONNECTIVITY_CHANGE", // 网络状态变更事件sticky: true, // 设为粘性事件data: { connected: false } }; commonEventManager.publishAsStickyCommonEvent(commonEvent);// 新用户安装APP后订阅 commonEventManager.subscribe({name: "CONNECTIVITY_CHANGE" }, (err, data) => {console.log("当前网络状态:" + data.connected);// 新订阅者立即收到最后一次网络状态! });
- 适用场景
- 系统状态通知(比如电池电量、网络连接)
- 全局配置变更(比如深色模式切换)
1.2.3 关键对比
特性 | 无序事件 | 有序事件 | 粘性事件 |
---|---|---|---|
接收顺序 | 随机 | 按优先级 | 按订阅顺序 |
事件存续 | 瞬态 | 瞬态 | 持久化存储 |
是否可中断 | 否 | ✅ 可中断链 | 否 |
发布权限 | 所有应用 | 所有应用 | 仅系统应用 |
后订阅收历史事件 | 否 | 否 | ✅ 能接收到 |
典型用例 | 通知推送 | 流程审批 | 系统状态同步 |
1.2.4 为什么黏性事件需要特殊权限
-
安全分险
- 粘性事件长期驻留内存
- 可能包含敏感信息(如网络状态)
-
资源消耗
```mermaid graph LRA[持久化存储] --> B[内存占用]A --> C[磁盘空间]
-
权限申请: 在
module.json5
中添加:
"requestPermissions": [{"name": "ohos.permission.COMMONEVENT_STICKY"}
]
2. 公共事件的运作机制
每个应用都可以按需订阅公共事件,订阅成功,当公共事件发布时,系统会将其发送给对应的应用。这些公共事件可能来自系统,其他应用和应用自身
2.1 安全注意事项
-
公共事件发布方
:如果不加限制,任何应用都可以订阅公共事件并读取相关信息,应避免在公共事件中携带敏感信息。采用以下方式,可以限制公共事件接收方的范围。
- 通过CommonEventPublishData中的subscriberPermissions参数指定订阅者所需权限。
- 通过CommonEventPublishData中的bundleName参数指定订阅者的包名。
-
公共事件订阅方
:订阅自定义公共事件后,任意应用都可以向订阅者发送潜在的恶意公共事件。采用以下方式,可以限制公共事件发布方的范围。
- 通过CommonEventSubscribeInfo中的publisherPermission参数指定发布者所需权限。
- 通过CommonEventSubscribeInfo中的publisherBundleName参数参数指定发布者的包名。
-
自定义公共事件名称应确保全局唯一,否则可能与其他公共事件冲突。
3. 动态订阅公共事件
3.1 场景介绍
动态订阅是指当应用在运行状态时对某个公共事件进行订阅,在运行期间如果有订阅的事件发布那么订阅了这个事件的应用将会收到改事件及其传递的参数
例如,某应用希望在运行期间收到电量过低的事件,并根据该事件降低其运行功耗,那么该应用便可动态订阅电量过低事件,收到该事件关闭一些非必要的任务来降低功耗
- 定义:应用程序在运行时订阅特定公共事件的机制
- 特点:仅在应用处于前台运行状态时有效(后台不接受事件)
- 生命周期:从订阅开始到应用进程结束/主动退订
3.2 工作流程图展示
3.3 完整开发流程概览
4. 公共事件的发布和订阅的使用步骤
4.1.1 公共事件的发布
当需要发布某个自定义公共事件时,可以通过publish()方法发布事件。发布的公共事件可以携带数据,供订阅者解析并进行下一步处理。
1. 简单事件发布(无数据)
// 导入必要模块
import { commonEventManager } from '@kit.BasicServicesKit';// 发布"任务完成"通知
commonEventManager.publish('TASK_COMPLETED', (err) => {if (err) {console.error('发布失败', err)} else {console.log('任务完成通知已发送')}
});
2. 传递数据的事件发布(带温度信息)
import { commonEventManager } from '@kit.BasicServicesKit';// 准备事件数据
const eventData = {code: 200, // 温度事件代码data: "23.5°C", isSticky: true // 新订阅者也应知道当前温度
};// 发布带温度的天气事件
commonEventManager.publish('TEMPERATURE_UPDATE', eventData, (err) => {if (err) {console.error('温度发布失败', err)} else {console.log('当前温度已更新')}
});
4.1.2 公共事件的订阅
场景介绍
动态订阅是指当应用在运行状态时对某个公共事件进行订阅,在运行期间如果有订阅的事件发布那么订阅了这个事件的应用将会收到该事件及其传递的参数。
例如,某应用希望在其运行期间收到电量过低的事件,并根据该事件降低其运行功耗,那么该应用便可动态订阅电量过低事件,收到该事件后关闭一些非必要的任务来降低功耗。
订阅部分系统公共事件需要先申请权限,订阅这些事件所需要的权限请见公共事件权限列表
从鸿蒙系统服务模块导入commonEventManager
,这是鸿蒙提供的事件管理工具,类似于浏览器的EventBus或Node.js的EventEmitter
class SubscribeClass {subscriber?: commonEventManager.CommonEventSubscriber
使用步骤
1.创建事件订阅类
- 定义
SubscribeClass
类作为一个事件中心 - 声明
subscriber
属性 - 用来存储订阅者ID(就像你的订阅凭证)
subscribe(event: string, callBack: (value: string) => void) {this.subscriber = commonEventManager.createSubscriberSync({ events: [event] })commonEventManager.subscribe(this.subscriber, (err, value) => {callBack(value.data as string)})
}
2.订阅功能详解
就像订阅杂志:
createSubscriberSync({ events: [event] })
→ 告诉系统"我想订阅这本周刊"(注册订阅者)commonEventManager.subscribe(...)
→ 实际订阅动作(err, value) => { callBack(...) }
→ 当新期刊发布,系统通知你(回调) → 你收到后做处理(执行你提供的callBack函数)
3.调用示例:
subscriberClass.subscribe('NEWS_UPDATE', (content) => {console.log('收到新闻:', content)
})
publish(event: string, data: string = '') {commonEventManager.publish(event, { data }, () => {})
}
5.HarmonyOS 公共事件机制实现卡片通信
在 HarmonyOS 中,卡片(Widget)是轻量化的 UI 组件,而公共事件机制是实现卡片与应用主程序之间通信的核心桥梁。让我通过一个天气预报卡片的完整案例来解释
1.主应用向卡片发送数据(当应用主程序需要更新卡片显示时)
// 在应用主程序中
import { commonEventManager } from '@kit.BasicServicesKit';// 准备要发送的天气数据
const weatherData = {city: "北京",temp: "26℃",condition: "晴"
};// 发布带数据的公共事件
commonEventManager.publish('WEATHER_CARD_UPDATE', { data: JSON.stringify(weatherData), isOrdered: false
}, (err) => {if (err) {console.error('天气更新失败');} else {console.log('卡片天气信息已更新');}
});
2.卡片向主应用发送请求
卡片需要主动请求更新数据时(如用户刷新按钮)
// 在卡片服务提供者(CardProvider)中
import { commonEventManager } from '@kit.BasicServicesKit';
import { formHost } from '@kit.FormKit';export class WeatherCardProvider extends FormExtensionAbility {// 处理卡片刷新操作按钮onFormEvent(formId: string, message: string) {if (message === 'refresh') {// 发布请求刷新事件commonEventManager.publish('REQUEST_WEATHER_UPDATE', { formId }, (err) => {} // 回调省略);}}// 接收主应用更新(在CardProvider中订阅事件)setupEvents() {commonEventManager.subscribe('WEATHER_CARD_UPDATE', (err, event) => {if (!err) {const data = JSON.parse(event.data);// 更新卡片UIformHost.updateForm(formId, {temperature: `${data.temp}`,weatherIcon: this.getWeatherIcon(data.condition)});}});}
}
3.主应用订阅卡片请求事件
// 在应用主程序入口(如EntryAbility)
import { BusinessError, commonEventManager } from '@kit.BasicServicesKit';
import { featureAbility } from '@kit.AbilityKit';export default class EntryAbility extends Ability {onCreate() {// 订阅卡片数据请求事件commonEventManager.subscribe('REQUEST_WEATHER_UPDATE', (err: BusinessError, event) => {if (!err) {// 调用天气API获取最新数据this.fetchWeatherData().then(latestData => {// 更新指定卡片this.updateSpecificCard(event.formId, latestData);});}});}// API获取最新天气数据private fetchWeatherData(): Promise<WeatherData> {/* API实现省略 */}// 更新指定卡片private updateSpecificCard(formId: string, data: WeatherData) {// 发布更新事件(带卡片ID)commonEventManager.publish('UPDATE_CARD_' + formId, {data: JSON.stringify(data)});}
}