首页/新闻资讯/正文详情

Hippy React 终端事件(Native Event)完整指南:从 Hippy.on 到 EventBus 的全局事件管理实战

发布时间:2026/9/25 5:59:32 来源:云帆数科 栏目:资讯中心
Hippy React 终端事件(Native Event)完整指南:从 Hippy.on 到 EventBus 的全局事件管理实战
跨平台移动开发前端【免费下载链接】HippyHippy is designed to easily build cross-platform dynamic apps. 项目地址https://gitcode.com/gh_mirrors/hi/Hippy点击查看免费下载本篇技术指南聚焦 Hippy 跨端框架中Hippy React 终端事件全局事件的管理机制屏幕旋转、网络变化、容器尺寸改变、JS 引擎销毁这类发给整个业务而非单个 UI 节点的事件应如何在前端监听、分发与卸载。读完本文你将掌握Hippy.on/off/emit、HippyEventEmitter与EventBus三套 API 的完整用法、各自的适用场景、事件底层分发链路EventDispatcher/HippyEventHub以及dealloc、destroyInstance、onSizeChanged等关键内置事件的正确使用姿势与源码级原理。什么是终端事件在 Hippy 业务开发中大部分事件点击、滑动、输入是绑定在单个 UI 节点上的由组件自身的 props 处理。但还有一类事件不是发给单个 UI而是发给整个业务例如屏幕的翻转rotate网络状态的变化networkStatusDidChange容器大小改变onSizeChanged如屏幕旋转、折叠屏切换JS 引擎销毁dealloc、界面节点销毁destroyInstance。这类事件统称为终端事件Native Event。Hippy React 为它们提供了两套管理方式见 native-event.md管理方式适用场景是否框架相关Hippy.on/Hippy.off/Hippy.emit监听dealloc、destroyInstance等特殊 C 底层事件也可手动定制 JS 全局事件框架无关HippyEventEmitter与EventBus2.15.0 后支持所有全局NativeEvent的统一分发如rotate也可手动定制 JS 全局事件Hippy React 定制 EventBus需要说明的是Hippy.on/off/emit实际是框架无关的底层全局事件监听器它在 driver/js/lib/global/Others.js 中被定义为Hippy.on on; Hippy.off off; Hippy.emit emit底层基于__GLOBAL__.globalEventHandle上每个事件名对应的Set集合维护监听器见 Others.js 中on/off的实现。emit触发时遍历Set中的监听器并逐个调用见 Others.js。HippyEventEmitter全局 NativeEvent 的监听入口HippyEventEmitter是 Hippy React 定制的 EventBus 之一所有全局NativeEvent都由其分发。以终端向前端发送rotate事件携带参数result为例import { HippyEventEmitter } from hippy/react; const hippyEventEmitter new HippyEventEmitter(); this.call hippyEventEmitter.addListener(rotate, evt console.log(evt.result));注意HippyEventEmitter无需反复实例化建议全局只初始化一次来复用。这与它的实现有关——在 emitter.ts 中构造函数接受一个可选的sharedListeners对象当多个实例共享同一个sharedListeners时它们会复用同一批底层HippyEventListener注册表避免同一个事件被重复注册到原生侧。从源码看addListener的完整行为见 emitter.ts校验event必须为字符串、callback必须为函数否则抛出TypeError(Invalid arguments for addListener)内部通过getNameForEvent生成eventEmitter_${event}形式的 key见 emitter.ts在hippyEventListeners中查找是否已存在该事件的HippyEventListener不存在则新建调用HippyEventListener.addCallback注册回调拿到一个数字形式的listenerId返回一个EventEmitterRevoker卸载器供后续移除监听使用。HippyEventEmitter 的其他能力除addListener外HippyEventEmitter还提供见 emitter.tsremoveAllListeners(event)移除指定事件的全部监听器并调用底层unregister()后删除注册表条目listenerSize(event)获取指定事件的监听器数量静态emit(event, param)触发事件通过EventDispatcher.getHippyEventHub获取事件中心并调用notifyEvent(HippyEventEmitter as any).emit HippyEventEmitter.prototype.emit将该方法挂到构造函数上因此可以直接HippyEventEmitter.emit(eventName, data)调用。事件卸载使用 revoker.remove()如果不需要使用的时候记得调用一下移除监听的方法一般放在组件的卸载生命周期中执行this.call.remove()这里的this.call正是addListener返回的EventEmitterRevoker。其实现见 emitter-revoker.ts它持有listenerId与绑定监听器bindListener调用remove()时执行bindListener.removeCallback(this.callback)并清空内部引用保证同一卸载器不会被重复移除。removeCallback的底层逻辑在 listener.ts它从EventDispatcher拿到该事件的HippyEventHub移除对应callbackId的事件处理器同时从listenerIdList数组中剔除该 id。最佳实践在 React 组件的componentWillUnmount或对应的卸载生命周期中调用remove()避免事件回调泄漏、导致组件卸载后仍被终端事件触发。EventBus2.15.0 之后推荐使用的全局事件对象2.15.0版本后Hippy React 增加了EventBus全局事件对象推荐采用该对象管理全局事件。与HippyEventEmitter不同EventBus是一个静态对象无需实例化且所有方法都返回EventBus本身天然支持链式调用。其完整实现位于 global-listener.ts并从hippy/react入口统一导出见 index.ts。最低支持版本2.15.0。on监听全局事件签名(events: string | string[], callback: (data?: any) void) EventBusevents: string | string[]指定事件名称。传入字符串时用于绑定单个事件传入数组时用于同时绑定多个事件。callback: (data?: any) void指定回调函数该回调函数可以作为EventBus.off的第二个参数。import { EventBus } from hippy/react; const rotateCallback (data) { console.log(rotate data, data data.orientation); } const accountChanged (data) { console.log(accountChanged data, data data.user); } // 链式调用注册事件 EventBus .on(rotate, rotateCallback) .on(accountChanged, accountChanged); /* 可以通过数组同时注册两个事件 EventBus.on([rotate1, rotate2], rotateCallback) */从源码看EventBus.on会先做参数校验非字符串/非数组、callback 非函数时抛出TypeError数组形式会遍历逐个调用内部addListener内部使用一个模块级的globalEventListeners对象按事件名缓存HippyEventListener与eventMapMapcallback, listenerId保证同一事件的多个回调互不干扰见 global-listener.ts。off移除全局事件签名(events: string | string[], callback?: (data?: any) void) EventBus两种用法只提供事件名称 → 移除对应事件的所有回调函数同时提供事件名称和回调函数 → 只移除事件上指定的回调函数。events: string | string[]指定事件名称支持单个字符串或数组批量移除。callback?: (data?: any) void可选参数与EventBus.on第二个参数对应为空时移除对应事件的所有监听器。import { EventBus } from hippy/react; const rotateCallback (data) { console.log(rotate data, data data.orientation); } EventBus.on(rotate, rotateCallback); // 只移除事件上指定的回调函数 EventBus.off(rotate, rotateCallback); // 移除对应事件的所有回调函数 EventBus.off(rotate);源码行为见 global-listener.ts未注册过的事件off会调用warn提示Event [${event}] has not been registered yet in EventBus不会抛错未传callback调用eventListener.unregister()注销原生事件中心清空eventMap并删除globalEventListeners条目传了callback通过eventMap.get(callback)找到对应的listenerId并调用removeCallback移除当eventMap.size 0时自动清理整个事件条目。emit触发全局事件签名(event: string, ...param: any) EventBusevent: string指定事件名称只能传单个事件。...param: any可选支持发送多个参数用作回调函数的参数。import { EventBus } from hippy/react; const rotateCallback (data1, data2) { console.log(rotate data, data1, data2); } EventBus.on(rotate, rotateCallback); // 触发 rotate 事件并携带参数 EventBus.emit(rotate, { orientation: vertical }, { degree: 90 });源码行为见 global-listener.tsemit通过EventDispatcher.getHippyEventHub(event)获取事件中心若事件尚未注册则warn提示并直接返回EventBus否则调用eventHub.notifyEvent(...param)把参数原样透传给所有已注册回调。注意emit本身只负责触发 JS 侧已注册的回调终端事件的实际来源是原生侧通过桥接向 JS 分发。sizeOf获取回调数量签名(event: string) numberevent: string指定事件名称。import { EventBus } from hippy/react; const rotateCallback1 (data) { console.log(rotate data, data data.orientation); } const rotateCallback2 (data) { console.log(rotate data, data data.orientation); } EventBus.on(rotate, rotateCallback1); EventBus.on(rotate, rotateCallback2); // 获取 rotate 事件所绑定的回调函数数量 console.log(EventBus.sizeOf(rotate)); // 2;源码实现见 global-listener.ts直接返回eventMap.size未注册的事件返回0。事件分发链路EventDispatcher 与 HippyEventHub理解终端事件的关键在于它背后统一的分发中心。HippyEventEmitter、EventBus的事件注册与触发最终都收敛到 dispatcher.ts 中的EventDispatcherregisterNativeEventHub(eventName)为事件名创建/复用HippyEventHub并存入模块级eventHubsMap见 dispatcher.tsgetHippyEventHub(eventName)按事件名取出事件中心见 dispatcher.tsreceiveNativeEvent(nativeEvent)接收原生侧传来的[eventName, eventParams]二元数组找到对应事件中心后调用notifyEvent(eventParams)完成分发见 dispatcher.ts。也就是说终端事件从前端视角看是监听-回调模型但从原生到 JS 的完整链路是原生模块 → 桥接调用receiveNativeEvent→HippyEventHub.notifyEvent→ 所有已注册回调。这也是EventBus.emit能触发HippyEventEmitter.addListener注册的同一事件回调反之亦然的根本原因——两者共用同一套事件中心注册表。另外EventDispatcher在初始化时被挂到global.__GLOBAL__.jsModuleList.EventDispatcher见 dispatcher.ts供原生桥接侧直接调用。JS 引擎销毁事件 dealloc最低支持版本2.3.4。当 Hippy JS 引擎被销毁前会触发dealloc事件能够保证回调函数里的最后一句 JS 代码被执行到。Hippy 业务可以通过监听dealloc事件做一些离开时的操作但回调函数不能使用asyncHippy.on(dealloc, () { // do something });从源码看该事件由两个层面配合实现JS 侧挂钩在 driver/js/lib/global/Event.js 中定义了global.HippyDealloc () { if (global.Hippy) { global.Hippy.emit(dealloc); } }把引擎销毁转译为一次Hippy.emit(dealloc)引擎侧调用在 driver/js/src/scope.cc 的Scope::WillExit()中先创建一个std::promise/future同步等待再向 JS 任务队列投递一个任务从全局对象上取出名为HippyDealloc的函数kDeallocFuncName[] HippyDealloc见 scope.cc校验其是函数后同步CallFunction执行最后future.get()阻塞等待 JS 执行完毕。这正是能保证回调里最后一句 JS 被执行到的原理引擎销毁流程会同步等待HippyDealloc执行完成才继续退出。也正因如此回调中不应使用async异步任务不会在同步等待窗口内完成避免出现离开前操作未执行完的竞态。界面节点销毁事件 destroyInstance最低支持版本2.3.4。当 RootView 被卸载时触发destroyInstance事件。与dealloc不同的是该事件早于dealloc触发且不会阻塞 JS 线程Hippy.on(destroyInstance, () { // do something });源码佐证见 driver/js/lib/global/Event.js 的__unloadInstance__实现global.__unloadInstance__ ((obj) { const { id } obj || {}; global.Hippy.emit(destroyInstance, id); Hippy.bridge.callNative(RootViewManager, removeRootView, id); });可以看到卸载实例时先emit(destroyInstance, id)此时还会把 RootView 的id作为参数传给回调随后才调用原生移除 RootView。由于该路径不涉及引擎级同步等待区别于WillExit的future.get()阻塞回调执行不会阻塞 JS 线程同时它发生在 RootView 真正被移除之前因此早于引擎销毁时的dealloc。两者可以配合使用destroyInstance适合做与界面节点相关的清理不阻塞、可异步dealloc适合做引擎退出前的最终收尾。容器大小改变事件 onSizeChangediOS 最低支持版本2.16.0。当容器大小改变时如屏幕旋转、折叠屏切换等会触发onSizeChanged事件import { HippyEventEmitter } from hippy/react; const hippyEventEmitter new HippyEventEmitter(); hippyEventEmitter.addListener(onSizeChanged, ({rootViewId, oldWidth, oldHeight, width, height}) { // rootViewId: root view id; oldWidth: 旧的宽度oldHeight: 旧的高度width: 新的宽度; height: 新的高度; console.log(size, rootViewId, oldWidth, oldHeight, width, height); });回调参数说明参数含义rootViewIdroot view 的 idoldWidth改变前的宽度oldHeight改变前的高度width改变后的宽度新宽度height改变后的高度新高度Android 侧的发送实现在 framework/android/src/main/java/com/tencent/mtt/hippy/HippyEngineManagerImpl.javaonSizeChanged(int rootId, int w, int h, int ow, int oh)被回调后将新旧尺寸组装成HippyMap宽高均经PixelUtil.px2dp从像素转换为 dp然后调用EventDispatcher.receiveNativeEvent(onSizeChanged, hippyMap)送入 JS 侧——正是上文介绍的receiveNativeEvent分发链路。这也解释了为什么前端回调拿到的尺寸单位是 dp 而非像素。实战内置模块如何复用终端事件HippyEventEmitter不只是给业务自定义事件用的Hippy React 的内置模块本身就是其典型用户。理解这些现成案例有助于你在业务中照同样的模式组织事件。网络状态变化NetInfo在 driver/js/packages/hippy-react/src/modules/network-info.ts 中NetInfo 模块内部维护了一个单例const NetInfoEventEmitter: HippyEventEmitter new HippyEventEmitter()第 37 行把业务传入的change事件名映射到真实的原生事件networkStatusDidChange第 34 行再通过addListener注册回调const DEVICE_CONNECTIVITY_EVENT networkStatusDidChange; const handler NetInfoEventEmitter.addListener(event, (data) { listener(data); });其中有个值得借鉴的细节见 network-info.tsaddEventListener先用NetInfoEventEmitter.listenerSize(event)判断该事件是否已有监听器只有当计数小于 1 时才调用Bridge.callNative(NetInfo, addListener, event)向原生侧注册避免同一原生事件被重复订阅移除最后一个监听器后再调用原生removeListener第 104-107 行。这就是全局只初始化一次、按需注册/注销原生监听的参考实现。Android 返回键BackAndroid在 driver/js/packages/hippy-react/src/modules/back-android.ts 中同样维护了模块级单例const hippyEventEmitter new HippyEventEmitter()第 26 行在initEventListener()中通过hippyEventEmitter.addListener(hardwareBackPress, ...)监听 Android 硬件返回键事件再按订阅顺序逆序遍历backPressSubscriptions决定是否执行默认退出行为见 back-android.ts。自定义终端事件JS 侧手动 emit除了监听原生分发的事件三套 API 也都支持在 JS 内部手动定制全局事件。最简单的做法是直接使用Hippy.emit框架无关、不依赖 Hippy React 的事件中心注册表Hippy.emit(myCustomEvent, { key: value }); // 对应监听 Hippy.on(myCustomEvent, (data) console.log(data.key));若希望自定义事件与 NativeEvent 共用同一套EventDispatcher分发体系例如业务内不同模块之间通过事件中心解耦则推荐使用EventBus.emit它可以触发任何通过HippyEventEmitter.addListener或EventBus.on注册的回调因为两者最终都汇聚到同一个HippyEventHub。同时记得在不使用时调用Hippy.off(myCustomEvent)或EventBus.off(myCustomEvent)清理监听器避免内存泄漏。小结如何选择事件管理 API需求推荐 API说明监听dealloc、destroyInstance等引擎/实例生命周期事件Hippy.on框架无关底层基于globalEventHandle的Set由 C/桥接层直接触发监听屏幕旋转、容器尺寸变化等 NativeEventHippyEventEmitter.addListener/EventBus.on共享EventDispatcher事件中心回调参数为原生透传数据业务内自定义全局事件EventBus2.15.0支持链式调用、数组批量注册、sizeOf统计推荐首选事件监听器复用与销毁addListener返回的 revoker 调remove()在组件卸载生命周期执行配合listenerSize做原生侧按需注册/注销本文所有结论均可在仓库中验证API 定义与实现见 driver/js/packages/hippy-react/src/event、driver/js/lib/global/Event.js 与 driver/js/lib/global/Others.js引擎销毁的同步等待逻辑见 driver/js/src/scope.ccAndroid 侧onSizeChanged的发送见 HippyEngineManagerImpl.java。业务集成时可按需阅读 native-event.md 及 hippy-react 模块文档 获取更多模块用法。赞分享跨平台移动开发前端【免费下载链接】HippyHippy is designed to easily build cross-platform dynamic apps. 项目地址https://gitcode.com/gh_mirrors/hi/Hippy点击查看免费下载相关推荐ego-browser 命令找不到PATH 配置与 ~/.local/bin 修复方案ego browser 命令找不到PATH 配置与 ~/.local/bin 修复方案 ➤ 最可能根因 shell 的 PATH 中缺少 ~/.local/AI 技能浏览器控制React Native FastImage 事件处理终极指南从 onLoadStart 到 onError 的完整解析React Native FastImage 事件处理终极指南从 onLoadStart 到 onError 的完整解析 React Native FastI移动开发前端UI组件UFO Galaxy 事件系统核心Event System Core实战指南EventBus、事件类型与观察者模式的完整解析UFO Galaxy 事件系统核心Event System Core实战指南EventBus、事件类型与观察者模式的完整解析 本篇技术指南围绕 UFO 项人工智能AI Agent自主智能体GUI 自动化Agent 编排多智能体RAG上一篇终极指南3分钟搞定Windows微信QQ防撤回告别消息消失的烦恼下一篇如何快速上手PyTorch Forecasting5分钟构建你的第一个预测模型创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关推荐

开源高性能Office转PDF解决方案MiniPdf盐技术解析
开源高性能Office转PDF解决方案MiniPdf盐技术解析

1. 项目背景与核心价值在办公自动化领域,文档格式转换一直是刚需场景。传统方案要么依赖商业软件(如Adobe套件),要么需要调用云端API(存在隐私风险)。而.NET生态此前缺乏一个真正开源、可商用、高性能的Off… · 2026/9/25 5:59:32

AIRI 记忆系统详解:如何用 DuckDB WASM + pgvector 为 AI 伴侣实现长期记忆
AIRI 记忆系统详解:如何用 DuckDB WASM + pgvector 为 AI 伴侣实现长期记忆

AIRI 记忆系统详解:如何用 DuckDB WASM pgvector 为 AI 伴侣实现长期记忆 【免费下载链接】airi 💖🧸 自托管、归你拥有的 Grok 风格 AI 伴侣与 waifu / 赛博生命灵魂容器,目标是接近 Neuro-sama 的高度;支持实时语音… · 2026/9/25 5:59:32

递归算法核心原理与经典案例解析
递归算法核心原理与经典案例解析

1. 递归思想的核心要义递归就像俄罗斯套娃,一个函数在执行过程中直接或间接调用自身,通过不断缩小问题规模最终解决原问题。这种"分而治之"的思想在计算机科学中占据着重要地位,其核心在于两个关键要素:基线条件&#x… · 2026/9/25 5:59:32

高云FPGA ILA调试实战:从配置失效到波形捕获的全流程解析
高云FPGA ILA调试实战:从配置失效到波形捕获的全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 6:24:06

游戏窗口拉不动?用SRWE的Force EXITSIZEMOVE开关解决Hotsampling失效问题
游戏窗口拉不动?用SRWE的Force EXITSIZEMOVE开关解决Hotsampling失效问题

游戏窗口拉不动?用SRWE的Force EXITSIZEMOVE开关解决Hotsampling失效问题 【免费下载链接】SRWE Simple Runtime Window Editor 项目地址: https://gitcode.com/gh_mirrors/sr/SRWE 玩窗口化游戏想拍高清截图,却遇到"改了分辨率游戏画面不跟… · 2026/9/25 6:24:06

CTF夺旗赛入门指南:从Web渗透到逆向分析的完整学习路径
CTF夺旗赛入门指南:从Web渗透到逆向分析的完整学习路径

1. CTF到底是个什么竞赛先说一句可能会得罪人的话:很多刚接触网络安全的人,是被"黑客""攻防""破解"这些词吸引进来的,但真正入行以后你会发现,CTF才是离"白帽思维"最近的训练场。CTF&… · 2026/9/25 6:24:06

ATGM332D RMC报文解析与北京时间转换实战:从原始数据到可用定位
ATGM332D RMC报文解析与北京时间转换实战:从原始数据到可用定位

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 6:24:00

ESP32换板为何不能直接运行?小智源码适配本质解析
ESP32换板为何不能直接运行?小智源码适配本质解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 6:24:00

CANoe中LIN诊断调度表4种切换模式深度解析
CANoe中LIN诊断调度表4种切换模式深度解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 6:23:59

数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)
数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码