HarmonyOS 6.0 应用级事件总线与组件间通信 组件通信有Prop/Link、Provide/Consume、Watch这些正规军。但有些场景它们搞不定——跨Ability通信、非父子组件通信、一对多广播、解耦通信。这时候就得用事件总线。HarmonyOS提供了Emitter和CommonEvent两套方案分别管应用内和应用间但很多人分不清什么时候用哪个。通信方案对比方案通信范围耦合度适用场景Prop/Link父子组件高父子数据同步Provide/Consume跨层级中祖孙组件通信AppStorage全局中全局状态共享Emitter应用内任意低组件间解耦通信CommonEvent跨应用低系统事件、跨应用通知Emitter是应用内的事件总线CommonEvent是系统级的事件广播。大多数场景用Emitter就够了CommonEvent只在需要跟系统或其他应用通信时才用。Emitter基础Emitter是kit.BasicServicesKit提供的进程内事件机制import{emitter}fromkit.BasicServicesKit;// 定义事件IDconstEVENT_LOGIN_SUCCESS1001;constEVENT_CART_UPDATED1002;// 发送事件emitter.emit({eventId:EVENT_LOGIN_SUCCESS,parameters:{userId:12345,userName:张三}});// 接收事件emitter.on({eventId:EVENT_LOGIN_SUCCESS},(eventData:emitter.EventData){letparamseventData.parameters;letuserIdparams[userId]asstring;this.onLoginSuccess(userId);});eventId是数字类型的事件标识自己定义。parameters携带数据类型是Recordstring, Object。关键Emitter是进程内的同一个应用内才能收发。跨进程不同Ability运行在不同进程时Emitter不生效。Emitter取消订阅// 订阅时保存回调引用privateloginCallback(eventData:emitter.EventData){// 处理}aboutToAppear():void{emitter.on({eventId:EVENT_LOGIN_SUCCESS},this.loginCallback);}aboutToDisappear():void{emitter.off(EVENT_LOGIN_SUCCESS,this.loginCallback);}必须在aboutToDisappear中off取消订阅否则组件销毁后回调还在执行导致内存泄漏和空引用crash。off必须传跟on相同的回调函数引用。所以回调不能写成匿名函数要保存为类成员变量。一对多广播Emitter天然支持一对多——多个组件订阅同一个eventIdemit时所有订阅者都会收到// 购物车页面emitter.emit({eventId:EVENT_CART_UPDATED,parameters:{count:5}});// TabBar组件emitter.on({eventId:EVENT_CART_UPDATED},(data){this.cartBadgedata.parameters[count]asnumber;});// 首页组件emitter.on({eventId:EVENT_CART_UPDATED},(data){this.refreshRecommendations();});// 消息中心emitter.on({eventId:EVENT_CART_UPDATED},(data){this.checkPromotions();});三个组件同时监听购物车更新事件emit一次三个都收到。这是Prop/Link做不到的——它们需要组件有明确的层级关系。登录状态同步经典场景登录成功后多个页面需要更新状态constEVENT_USER_STATE_CHANGED2001;// 登录页asynclogin(username:string,password:string):Promisevoid{letuserawaitthis.authService.login(username,password);AppStorage.setOrCreate(currentUser,user);emitter.emit({eventId:EVENT_USER_STATE_CHANGED,parameters:{isLogin:true,userId:user.id}});}// 退出登录logout():void{this.authService.logout();AppStorage.delete(currentUser);emitter.emit({eventId:EVENT_USER_STATE_CHANGED,parameters:{isLogin:false}});}// 个人中心页aboutToAppear():void{emitter.on({eventId:EVENT_USER_STATE_CHANGED},this.onUserStateChanged);}privateonUserStateChanged(data:emitter.EventData){letisLogindata.parameters[isLogin]asboolean;if(isLogin){this.showUserInfo();}else{this.showLoginButton();}}为什么不直接用AppStorageAppStorage的数据变化会触发UI刷新但不会触发逻辑回调。比如退出登录时你不仅要更新UI还要清理缓存、取消请求、重置状态——这些是业务逻辑不是UI渲染。Emitter可以触发这些逻辑回调。封装事件总线直接用emitter的API有点啰嗦。封装一个类型安全的事件总线classEventBus{privatestaticinstance:EventBus;privatehandlers:Mapnumber,emitter.Callback[]newMap();staticgetInstance():EventBus{if(!EventBus.instance){EventBus.instancenewEventBus();}returnEventBus.instance;}on(eventId:number,callback:emitter.Callback):void{letexistingthis.handlers.get(eventId);if(existingundefined){existing[];}existing.push(callback);this.handlers.set(eventId,existing);emitter.on({eventId:eventId},callback);}off(eventId:number,callback:emitter.Callback):void{letexistingthis.handlers.get(eventId);if(existing!undefined){letnewList:emitter.Callback[][];for(leti0;iexisting.length;i){if(existing[i]!callback){newList.push(existing[i]);}}this.handlers.set(eventId,newList);}emitter.off(eventId,callback);}emit(eventId:number,parameters?:Recordstring,Object):void{if(parameters!undefined){emitter.emit({eventId:eventId,parameters:parameters});}else{emitter.emit({eventId:eventId});}}}封装后使用更简洁EventBus.getInstance().on(EVENT_LOGIN,this.onLogin);EventBus.getInstance().emit(EVENT_LOGIN,{userId:123});EventBus.getInstance().off(EVENT_LOGIN,this.onLogin);CommonEvent跨应用通信CommonEvent是系统级广播可以跨应用、跨进程import{commonEventManager}fromkit.BasicServicesKit;// 订阅系统事件letsubscribercommonEventManager.createSubscriber({events:[usual.event.SCREEN_OFF]});commonEventManager.subscribe(subscriber,(err,data){// 处理屏幕关闭事件});// 取消订阅commonEventManager.unsubscribe(subscriber);CommonEvent需要权限声明。很多系统事件需要对应权限才能订阅。自定义事件不需要权限但要确保事件名的唯一性——建议用包名作前缀letCUSTOM_EVENTcom.example.app.ORDER_CREATED;CommonEvent发送发送自定义CommonEventcommonEventManager.publish(com.example.app.ORDER_CREATED,{parameters:{orderId:20240101001,amount:99.9}},(err){if(!err){console.info(Event published);}});publish是异步的回调在发送完成后触发。注意CommonEvent的发送和接收可以在不同应用中。这意味着你的事件可能被其他应用监听到敏感数据不要直接放在parameters里。什么时候用CommonEventCommonEvent的使用场景很明确监听系统事件屏幕开关、网络变化、时区变化、电量变化跨应用通知支付应用通知订单完成地图应用接收导航请求多进程通信同一个应用的UIAbility和ExtensionAbility在不同进程应用内通信不要用CommonEvent——它比Emitter重得多涉及跨进程序列化而且有权限和可见性问题。时序控制事件到达顺序不保证。如果A事件必须在B事件之前处理constEVENT_STEP1_COMPLETE3001;// A完成后发事件emitter.emit({eventId:EVENT_STEP1_COMPLETE});// B等A完成才执行emitter.on({eventId:EVENT_STEP1_COMPLETE},(){this.executeStep2();});用事件链代替时序假设——A完成后emit通知B监听通知再执行。不要假设emit是同步的。内存泄漏防护Emitter的回调如果不off组件销毁后依然执行。最危险的情况// 危险匿名函数无法offemitter.on({eventId:1001},(data){this.updateUI();// 组件已销毁this可能无效});// 安全保存引用组件销毁时offprivatehandler(data:emitter.EventData){this.updateUI();}aboutToAppear():void{emitter.on({eventId:1001},this.handler);}aboutToDisappear():void{emitter.off(1001,this.handler);}规则on和off必须成对出现回调必须是类成员变量不能是匿名函数。踩坑清单问题原因解决组件销毁后crash没off取消订阅aboutToDisappear中offoff不掉匿名函数无法比较引用回调保存为类成员变量事件收不到Emitter跨进程不生效跨进程用CommonEvent收到重复事件多次on同一回调on前先off事件顺序不对emit不保证同步时序用事件链传递顺序CommonEvent收不到没声明权限检查module.json5权限自定义事件被拦截事件名不够唯一用包名作前缀参数类型丢失parameters反序列化只传基本类型数据敏感泄露CommonEvent跨应用可见敏感数据加密或用Emitter事件总线内存增长Map中回调越积越多off时清理Map记录事件总线是组件通信的后门——不用它时架构清晰用了它时调用链隐晦。能用Prop/Link解决的不要用Emitter能不跨进程的不要用CommonEvent。但当通信距离超出组件树范围时事件总线就是最干净的解法。