HarmonyOS 事件监听为什么点击地图 Marker 没反应?事件绑定详解

HarmonyOS 事件监听为什么点击地图 Marker 没反应?事件绑定详解 文章目录前言一、什么是事件监听1.1 生活中的类比1.2 在代码中的形态二、项目源码中的 on/offMapKit 事件2.1 地图组件的两个核心事件三、自实现事件发布订阅系统3.1 手写一个简单的 EventEmitter3.2 在组件间通信中使用四、HarmonyOS emitter 模块系统级事件总线4.1 使用系统 emitter五、内存泄漏最容易忽略的问题5.1 内存泄漏的场景5.2 正确做法配对使用 on/off六、MapKit 事件类型速查总结前言在 HarmonyOS 开发中很多系统组件地图、传感器、网络状态采用事件监听on/off模式来通知应用程序发生了某件事。本项目地图页面的核心交互——点击 Marker 显示弹窗、点击定位按钮移动地图——都通过mapController.on(...)实现。理解事件监听模式是掌握 HarmonyOS SDK 的关键。本篇从原理到实战带你彻底搞懂 on/off 模式。一、什么是事件监听1.1 生活中的类比你订阅了外卖App的配送通知 on(配送到楼下, () { 下楼取餐 }) ← 注册监听 外卖员到楼下时 emit(配送到楼下) ← 触发事件由系统/框架完成 你下楼取餐 ← 你的回调被执行 你不需要外卖通知了 off(配送到楼下) ← 取消监听1.2 在代码中的形态// 注册事件监听订阅emitter.on(eventName,(data){// 事件发生时的处理逻辑});// 某个时刻系统触发事件emitter.emit(eventName,{key:value});// 取消监听退订防止内存泄漏emitter.off(eventName);二、项目源码中的 on/offMapKit 事件2.1 地图组件的两个核心事件在GasStationPage.ets的init()方法中this.callbackasync(err,mapController):Promisevoid{if(err){return;}this.mapControllermapController;// 事件1监听我的位置按钮点击this.mapController.on(myLocationButtonClick,(){Logger.info(testTag,Jump to my location);mapUtil.moveToMyLocation(mapController).then((){this.isShowtrue;// 显示弹窗});});// 事件2监听 Marker标记点点击this.mapController.on(markerClick,(marker){this.isShowtrue;// 显示弹窗marker.setInfoWindowVisible(true);// 显示信息窗this.curMarkermarker;// 记录当前 Markerthis.imageScale1.5;// 设置放大比例mapUtil.imageAnimation(marker,this.imageScale);// 执行放大动画// 移动地图到该加油站位置mapUtil.moveToCurrentPosition(marker.getPosition().latitude,marker.getPosition().longitude,mapController);});};事件流程用户点击地图上的 Marker │ ▼ MapKit 触发 markerClick 事件传入被点击的 marker 对象 │ ▼ 我们注册的回调函数执行 │ ├── isShow true → bindSheet 弹窗出现 ├── marker 放大动画 └── 地图相机移动到该位置三、自实现事件发布订阅系统3.1 手写一个简单的 EventEmitter理解 on/off 最好的方法是自己实现一个// 事件回调类型typeEventCallbackTunknown(data:T)void;// 事件发布订阅类classEventEmitter{// 用 Map 存储事件名 → 回调函数列表privatelisteners:Mapstring,EventCallback[]newMap();// 注册事件监听onT(eventName:string,callback:EventCallbackT):void{if(!this.listeners.has(eventName)){this.listeners.set(eventName,[]);}this.listeners.get(eventName)!.push(callbackasEventCallback);console.log([EventEmitter] 注册事件${eventName}当前监听数${this.listeners.get(eventName)!.length});}// 注册一次性监听触发一次后自动移除onceT(eventName:string,callback:EventCallbackT):void{constwrapper:EventCallbackT(data:T){callback(data);this.off(eventName,wrapperasEventCallback);// 执行后立即移除};this.on(eventName,wrapper);}// 触发事件emitT(eventName:string,data?:T):void{constcallbacksthis.listeners.get(eventName);if(!callbacks||callbacks.length0){console.warn([EventEmitter] 没有${eventName}的监听器);return;}callbacks.forEach(cbcb(dataasunknown));console.log([EventEmitter] 触发事件${eventName}通知了${callbacks.length}个监听器);}// 取消特定监听off(eventName:string,callback?:EventCallback):void{if(!callback){// 不传 callback移除该事件的所有监听this.listeners.delete(eventName);console.log([EventEmitter] 移除事件${eventName}的所有监听器);return;}constcallbacksthis.listeners.get(eventName);if(callbacks){constfilteredcallbacks.filter(cbcb!callback);this.listeners.set(eventName,filtered);}}// 清理所有监听组件销毁时调用removeAllListeners():void{this.listeners.clear();console.log([EventEmitter] 已清理所有事件监听器);}// 查看事件监听数量调试用listenerCount(eventName:string):number{returnthis.listeners.get(eventName)?.length??0;}}// 全局事件总线单例exportconstglobalBusnewEventEmitter();3.2 在组件间通信中使用// 定义事件类型避免拼写错误constEVENTS{STATION_SELECTED:station:selected,MAP_MOVED:map:moved,LOCATION_UPDATED:location:updated,}asconst;interfaceStationSelectedEvent{stationId:string;latitude:number;longitude:number;}// 组件A发布事件地图页面Componentstruct MapPage{onMarkerClick(stationId:string,lat:number,lng:number):void{// 发布加油站被选中事件globalBus.emitStationSelectedEvent(EVENTS.STATION_SELECTED,{stationId,latitude:lat,longitude:lng});}build(){// ...Text(点击触发事件).onClick((){this.onMarkerClick(001,40.0046,116.4823);})}}// 组件B订阅事件列表页面Componentstruct StationListPage{StateselectedId:string;aboutToAppear():void{// 注册监听globalBus.onStationSelectedEvent(EVENTS.STATION_SELECTED,(event){this.selectedIdevent.stationId;console.log(站点被选中${event.stationId});});}aboutToDisappear():void{// ⚠️ 必须在组件销毁时取消监听防止内存泄漏globalBus.off(EVENTS.STATION_SELECTED);}build(){Text(当前选中${this.selectedId})}}四、HarmonyOS emitter 模块系统级事件总线4.1 使用系统 emitterHarmonyOS 提供了系统级的kit.BasicServicesKit中的emitter模块import{emitter}fromkit.BasicServicesKit;// 定义事件ID用数字或字符串constSTATION_CLICK_EVENT:emitter.InnerEvent{eventId:1001};// 订阅事件在页面Aemitter.on(STATION_CLICK_EVENT,(data:emitter.EventData){conststationIddata.data?.[stationId]asstring;console.log(收到事件站点ID${stationId});});// 发布事件在页面Bemitter.emit(STATION_CLICK_EVENT,{data:{stationId:001,name:望京加油站}});// 取消订阅emitter.off(STATION_CLICK_EVENT);// 只订阅一次emitter.once(STATION_CLICK_EVENT,(data:emitter.EventData){console.log(只执行一次的回调);});五、内存泄漏最容易忽略的问题5.1 内存泄漏的场景Componentstruct LeakyComponent{aboutToAppear():void{// 注册了监听globalBus.on(someEvent,this.handleEvent);}handleEvent(){console.log(事件触发);this.doSomething();// 如果组件已销毁this 还在被引用};// ❌ 忘记在 aboutToDisappear 中取消监听// 结果组件销毁后globalBus 仍然持有 this 的引用// 导致组件无法被垃圾回收 → 内存泄漏// 更糟后续事件触发时已销毁的组件仍然被调用 → 可能崩溃build(){Text(组件)}}5.2 正确做法配对使用 on/offComponentstruct SafeComponent{// 保存回调引用用于精确取消privatehandleEvent:(data:unknown)void(){};aboutToAppear():void{// 保存回调函数引用this.handleEvent(data:unknown){console.log(事件触发数据,data);};// 注册监听globalBus.on(someEvent,this.handleEvent);}aboutToDisappear():void{// ✅ 组件销毁时精确取消该回调的监听globalBus.off(someEvent,this.handleEvent);}build(){Text(安全组件)}}提示on 和 off 必须成对出现。在aboutToAppear注册的监听必须在aboutToDisappear中取消。这是 HarmonyOS 开发的黄金法则。六、MapKit 事件类型速查事件名触发时机回调参数markerClick点击地图标记marker: map.MarkermyLocationButtonClick点击定位按钮无mapClick点击地图空白处latLng: mapCommon.LatLngcameraMove地图相机移动中cameraPositioncameraIdle地图相机停止移动cameraPositioninfoWindowClick点击信息窗marker: map.Marker总结事件监听on/off模式是 HarmonyOS 系统组件与应用代码解耦通信的核心机制。掌握三点①on注册监听并保存回调引用②事件触发时执行业务逻辑③off在组件销毁时精确取消监听避免内存泄漏。理解了这个模式你就能读懂 MapKit、传感器、网络状态等所有基于事件驱动的 HarmonyOS SDK。