在鸿蒙(HarmonyOS)原生应用开发中,随着业务模块的增多,组件间、页面间以及线程间的通信需求变得日益复杂。如果采用传统的层层回调或状态透传,会导致代码高度耦合且难以维护。为此,鸿蒙官方提供了一套完善的发布/订阅事件通信机制。

针对不同的通信范围,开发者主要使用以下三种核心方案:

一、 同UIAbility内的轻量级通信:EventHub

EventHub 是鸿蒙提供的一种基于发布-订阅模式的事件中心模块,类似于前端开发中的 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: Record<string, 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): Promise<void> {
        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}`);
    }
});

在落地事件总线机制时,开发者需特别注意以下工程陷阱:

  1. 严格的生命周期绑定:无论是 EventHub 还是 Emitter,都必须在组件的 aboutToDisappear 或页面的 onDestroy 中调用 off 取消订阅。未取消的订阅会导致组件实例无法被 GC 回收,引发严重的内存泄漏。
  2. 跨线程数据序列化限制:使用 Emitter 跨线程传递数据时,传递的对象必须是可序列化的普通对象,严禁传递包含 UI 装饰器(如 @State@Link)的复杂类型或闭包函数,否则会导致运行时崩溃。
  3. 避免事件风暴与死循环:在事件回调中再次触发同类事件极易导致无限递归。对于高频触发的事件(如滚动、滑动),必须在业务层加入防抖(Debounce)或节流(Throttle)机制。
  4. 权限与隐私合规:使用 CommonEventManager 订阅系统级事件时,务必在 module.json5 中检查并声明所需的系统权限(如 ohos.permission.GET_NETWORK_INFO),否则订阅将静默失败。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐