十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

React Native性能优化:Hermes引擎配置与实战指南

React Native性能优化:Hermes引擎配置与实战指南 1. 关于oh-my-hermes我为什么给React Native写了一套脚手架如果你做过一段时间的React Native开发看到oh-my-hermes这个名字大概率会心一笑——这摆明了是在致敬那个经典的oh-my-zsh。zsh是Shell的一种oh-my-zsh是社区里一套把zsh配置到极致好用的框架而Hermes是React Native的JS引擎oh-my-hermes就是我为Hermes准备的一套配置、脚本和最佳实践集合。先说清楚这套东西解决了什么问题。React Native从0.70开始把Hermes设置为Android端的默认JS引擎0.71之后iOS也默认开启。但默认开启不等于开箱即用。我踩过的坑包括打包后发现Android包体积比原来大出好几兆、Release模式下启动白屏但Debug一切正常、Flipper死活连不上Hermes调试器、某些第三方库在Hermes下直接抛异常。这些问题背后的核心原因只有一个Hermes和老的JavaScriptCoreJSC引擎的工作方式完全不同但很多教程、模板和第三方库还在按JSC的思路写。oh-my-hermes项目就是把这些经验沉淀成了一套可复用的工程方案。它的目标用户很明确准备迁移到Hermes、已经开启Hermes但遇到性能问题、或者单纯想把RN项目启动速度和内存占用优化到极致的人。它不是一个神奇轮子而是一个向导帮你把Hermes的配置、优化和坑位一次性理清。我在最早的版本里只写了一个README和几个脚本后来不断迭代加入了自动化配置命令、性能基线测试脚本、常见问题速查清单。文章后面你会看到这些工具本质上都是在回答三个问题Hermes到底改了什么我应该怎么配出了问题怎么查只要你弄懂这三个问题有没有oh-my-hermes这套工具其实无所谓。但如果不想重新踩一遍我趟过的坑直接用这套现成的方案会省下大量时间。2. 为什么选Hermes引擎切换背后的性能账2.1 Hermes到底干了什么在深入配置之前我必须先把Hermes的工作原理讲透否则后面那些配置项你只知其然不知其所以然。Hermes是一个专门为React Native打造的JavaScript引擎由Facebook现在是Meta开发核心优化思路是针对移动端场景重新设计。和传统的JSC不同Hermes最大的三个特点是预编译字节码、直接映射字符串、快照式内存管理。预编译字节码的意思很简单。JSC是运行时编译——JS代码在App运行的时候才被解析和编译成机器码这不可避免会造成启动延迟而Hermes支持在打包阶段就把JS代码编译成字节码HBC格式运行的时候直接执行字节码省掉了解析和编译的时间。实测下来一个中型RN应用纯启动阶段的JS执行时间能缩短30%到50%。直接映射字符串是内存优化。传统引擎在处理字符串时需要把源码里的字符串字面量复制到内存中Hermes则直接把编译好的字节码文件里的字符串段映射到内存不需要额外复制。相当于一本书你只需要翻开看而不用手抄一遍。对于拥有大量文案和配置字符串的业务代码这个优化能省下几MB内存。快照式内存管理是GC垃圾回收层面的优化。Hermes的GC不是简单的标记-清除而是基于对象分配快照来做内存管理配合编译期生成的对象布局信息GC效率比JSC高不少。我自己的测试里长时间使用场景下的内存抖动明显减少FPS曲线也更稳定。2.2 Hermes和JSC的关键差异我在博客里反复强调过一句话不要用JSC的思维来调Hermes。两者之间的差异直接影响你的技术选型和排错思路。从执行方式看JSC是JIT即时编译架构运行速度在某些纯计算场景下甚至比Hermes快Hermes则是AOT预编译为主通过提前编译换取启动速度和内存优势。如果你的App有大量复杂计算逻辑理论上JSC更快但移动端大部分性能瓶颈都在启动、首屏渲染和内存占用这些恰恰是Hermes的强项。从兼容性看JSC对ES标准的支持相对激进而Hermes早期版本对ES6的支持比较保守。比如Proxy对象在Hermes上是默认不支持的直到0.7x版本才提供了部分支持。很多老代码里的polyfill、第三方库特别是某些状态管理工具、数据监听库会用到Proxy在Hermes下直接崩。这个问题后面排查章节会详细讲。从调试体验看JSC时代你用Safari的Web Inspector调试iOS用Chrome DevTools调试AndroidHermes时代统一通过React Native DevTools基于Chrome DevTools Protocol调试Flipper也支持Hermes调试协议细节和之前差异很大。很多团队升级之后发现调试器连不上、断点不生效十有八九是没搞清楚Hermes调试器的连接流程。2.3 选型建议你的项目适不适合上Hermes在oh-my-hermes的工程模板里我写了一个迁移决策清单用来帮助团队判断是否应该开启Hermes。总结下来就是四问你的App是否对启动速度敏感启动耗时每多一秒用户流失率就高一截这种场景Hermes有明显优势。你的App是否有大量长列表、图片流等吃内存的场景Hermes的内存占用稳定性会让这类场景受益。你的项目是否依赖某些冷门JS库需要先排查这些库有没有用到Hermes不支持的API。你的团队是否有能力处理调试工具的切换如果整个团队还停留在Safari调试RN的阶段需要安排一次上手培训。如果你的项目不依赖冷门库、主要痛点是性能和内存那么放心开Hermes收益远大于成本。如果项目里有一堆无人维护的老库建议先用一个分支把Hermes跑起来跑通测试再合并。3. 核心配置详解从Android到iOS的一步步设置3.1 Android端开启Hermes的正确姿势React Native 0.70之后的版本Android端Hermes默认开启。但默认开启只针对通过npx react-native init创建的新项目老项目升级上来需要手动检查android/app/build.gradle文件。如果你打开android/app/build.gradle找到android区块会看到类似这样的配置android { compileSdkVersion 34 // ... defaultConfig { applicationId com.example.app minSdkVersion 21 targetSdkVersion 34 } }在React Native的Gradle插件配置里Hermes开关是通过react对象配置的react { enableHermes true // 老版本写法hermesEnabled true }注意不同RN版本这个属性的写法不一样。0.69及之前是hermesEnabled true0.70及以后统一改成了enableHermes true。如果你复制网上的旧代码很容易配了个寂寞。改完配置后需要重新构建cd android ./gradlew clean ./gradlew assembleRelease然后安装Release包测试。这里有个非常关键的点Hermes开启后Debug模式默认还是走Metro加载JS行为差异不大但Release模式会用hermesc编译器把JS bundle预编译成字节码。所以很多问题只在Release包中出现。我强烈建议在验证Hermes配置时不要只在Debug下测一定要打一个Release包。3.2 iOS端开启Hermes与Podfile调整iOS端的配置在ios/Podfile里核心是这一行use_react_native!( path: config[:reactNativePath], hermes_enabled: true )如果你是用React Native 0.71以上版本创建的项目默认值已经是true。老项目需要手动加这个参数然后重新安装依赖cd ios bundle install bundle exec pod install这里有个容易踩的坑如果你之前用pod install而不是bundle exec pod install可能安装到错误的CocoaPods版本导致Hermes的pod依赖解析失败。建议统一用bundle方式管理CocoaPods依赖。另外iOS端Hermes开启后构建时间会变长因为Xcode需要额外编译Hermes的静态库。如果你的Mac配置一般第一次构建可能要多等几分钟这是正常的不要急着杀进程。3.3 如何确认Hermes真的开启了配完之后怎么确认Hermes真的生效我总结了三种方法第一种全局搜索字符串。在Release包中Hermes会暴露一个全局变量HermesInternal。你在App启动后的某个地方比如开发者菜单或者首屏渲染日志中执行console.log(global.HermesInternal?.getRuntimeProperties());如果能看到一个包含Build、BytecodeVersion等字段的对象说明Hermes已经生效。如果打印结果是undefined说明还是JSC在跑。第二种查看包结构。Android的APK解压后在lib/armeabi-v7a或lib/arm64-v8a目录下应该能看到libhermes.so文件。iOS的app包里Frameworks目录下应该有Hermes.framework。第三种检查发布日志。Hermes编译字节码时Metro打包日志会多出类似info Running hermesc的提示看到这个基本就稳了。4. 用oh-my-hermes的脚本做性能基线测试4.1 为什么要测基线而不是等用户反馈很多团队的性能优化是用户反馈卡顿才查这其实已经晚了。性能问题应该量化、可对比、可持续追踪。oh-my-hermes提供了一套性能基线测试脚本核心思路非常简单在开启Hermes前后分别记录启动时间、内存峰值、帧率三个指标然后对比。我已经把脚本封装到项目里了原理上就是通过adb命令采集数据。Android端启动时间可以通过adb shell am start -W获取adb shell am start -W -n com.example.app/.MainActivity输出里的TotalTime就是冷启动时间。注意不要在连接了USB调试的情况下测因为adb本身会拉高CPU占用影响数据准确性。内存数据可以通过adb shell dumpsys meminfo采集adb shell dumpsys meminfo com.example.app重点看Java Heap、Native Heap和Total PSS三个字段。Hermes优化后Native Heap通常会下降因为JS代码解析和字符串映射带来的内存减少了。帧率统计相对复杂一点我用的是gfxinfo的framestatsadb shell dumpsys gfxinfo com.example.app framestats脚本会解析最近128帧的绘制时间计算jank率掉帧比例。这个指标能反映列表滚动和页面切换时的流畅度。4.2 我测出来的真实数据变化用一套中等体量的RN应用做样本业务代码大概500个页面、600多个第三方依赖在开启Hermes前后我采集到的数据如下指标JSCHermes变化幅度冷启动时间中位数1840ms1320ms降低约28%冷启动时间P953120ms2210ms降低约29%Native Heap内存峰值186MB141MB降低约24%列表滚动jank率8.2%4.7%降低约43%JS线程CPU占用率24%16%降低约33%启动时间降低28%是什么概念对于用户来说从点击App图标到看到首屏内容体感差距非常明显。特别是中低端Android机原本要转3秒以上白屏现在明显快了很多。内存方面Native Heap降低24%的收益也很大。现在很多App都有WebView、地图SDK这类吃内存大户RN层每省下一点多进程场景下的OOM风险就小一点。4.3 性能基线如何接入CI脚本光本地跑意义有限我后来把性能基线测试接入了CI流水线。思路是在每次合并到主干前自动跑一次启动时间和内存测试如果相对上次基线退化超过10%就阻断合并并给出warning。这里必须提醒一个坑CI机器和开发机性能不一样测试绝对值没有参考意义。正确做法是固定一台测试设备最好就是一台闲置的安卓真机每次都在同一台设备上测同时保证App版本、测试网络最好是飞行模式一致。只有控制变量数据才可比。5. 实操过程中的高频问题和排查实录5.1 Hermes下的调试工具连不上这是问得最多的问题。症状是Flipper打开后看不到Hermes调试器选项或者React Native DevTools连接上了但Console里不打印日志、断点不生效。原因在于Hermes时代调试流程变成了Metro - Hermes调试器 - Chrome DevTools Protocol。必须保证Metro在运行而且App是通过Metro加载的JS。Android Debug模式下App默认通过Metro加载JS所以没问题但如果打了Release包JS是编译进字节码的此时Hermes调试器无内容可调试Console全部静默这属于正常现象。想调试就必须用Debug模式。如果Debug模式也连不上检查Metro的启动参数。推荐用--hostType lan而不是--hostType localhost特别是用真机调试时否则手机会找不到Metro。5.2 Hermes下崩溃但堆栈全是乱码Hermes的字节码在Release包里不会保留原始的JS函数名崩溃日志里看到的堆栈可能只有十六进制地址。这个问题曾经直接劝退了很多踩坑者实际解决方法很简单在打包时生成source map然后符号化。Android端在android/app/build.gradle里配置android { buildTypes { release { sourceMapFile ${rootDir}/sourcemaps/index.android.bundle.map } } }然后在打包脚本里加上npx react-native bundle \ --platform android \ --dev false \ --entry-file index.js \ --bundle-output android/app/src/main/assets/index.android.bundle \ --sourcemap-output android/sourcemaps/index.android.bundle.map拿到map文件后用react-native-symbolicate-stack或者source-map库就能还原出原始JS堆栈。iOS端原理一样在Xcode的Build Phase里开启Generate JS Source Maps。5.3 某些第三方库在Hermes下报错典型场景项目里用了某个老版本的状态管理库在JSC下跑得好好的切换到Hermes后直接报TypeError: xxx is not a function。看堆栈指向的是库内部代码而你的业务代码完全没改。八成是这个库用到了Hermes不支持的ES特性。用前面提到的HermesInternal.getRuntimeProperties()可以打印Hermes支持的特性集合。如果确认缺了某特性解决方案有优先级第一优先升级这个库到最新版本大概率已经兼容Hermes。第二优先用babel插件做语法转换比如babel/plugin-transform-proxy-compat可以在编译期处理部分Proxy用法。第三优先在业务入口手动polyfill但只建议做临时方案。终极方案如果这个库实在无法兼容且无法替换那只能放弃对部分页面开启HermesRN支持按平台/x86架构维度做条件编译。5.4 Android包体积变大很多人在升级Hermes后发现APK体积变大第一反应是这优化了个寂寞。冷静分析一下其实变大的主要是libhermes.so这个动态库大概会增加3-4MB。对于现代手机来说这点体积换来性能和内存的收益完全可以接受。如果你确实对包体积有强要求可以从两个方向压缩一个是启用bundleInHermes字节码压缩模式这需要额外配置另一个是使用Hermes的字节码裁剪工具但这类操作复杂度高、收益有限我只在文档里提了一句不建议大多数人尝试。还有一个更实际的方向检查自己的so库裁剪配置。build.gradle里可以只保留arm64-v8a一种ABI去掉armeabi-v7a和x86APK能瘦身不少。注意这样做了之后老款32位设备将无法安装需要根据你的用户设备分布来决定。5.5 常见问题速查表问题现象可能原因解决方向Debug模式正常Release模式白屏JS bundle没有被hermesc正确编译清理构建缓存检查打包命令中的hermesc日志Hermes调试器一直connectingMetro主机地址配置错误使用--hostType lan并确保手机和电脑同一局域网启动后Native内存不降反升Hermes字节码和图片缓存叠加查看Memory Graph重点排查图片解码缓存低端机偶发JS线程卡顿Hermes GC触发的长停顿检查是否存在短时间内大量创建对象尝试优化业务代码React Navigation转场动画卡顿Hermes下帧回调时机不同升级react-native-screens开启enableScreens优化6. 深入优化内存、启动速度和包体积极限压榨6.1 启动速度的进阶优化思路不要以为开了Hermes就万事大吉。启动提速是组合拳Hermes负责JS执行提速但你的业务代码结构同样重要。我常用的一个优化方法是启动路径拆分。把首屏非必需的模块全部改为动态import通过React.lazy或者require.context延迟加载。配合Hermes的字节码预编译首屏只用执行最小模块集合启动时间还能再降10%到15%。还有一个容易忽略的细节图片解码速度。首屏如果有大图JS执行再快图片解码慢也会拖累视觉首屏时间。建议把首屏图压缩到合理尺寸或者改用WebP格式。6.2 内存优化的隐藏玩法Hermes的快照式内存管理对对象布局有优化但前提是代码不能制造大量内存碎片。我踩过的一个坑是在做长列表时每行都创建了新的内联对象。JSC时代影响不大但Hermes下会导致频繁GC。改成把行组件拆成纯展示组件数据结构扁平化内存抖动明显改善。另外如果你用了Redux建议把store的层级拍平。深层嵌套的state对象在Hermes下的访问和更新开销更高。用normalizr之类的工具把嵌套结构转成扁平map收益显著。6.3 Hermes与新架构的搭配React Native新架构Fabric TurboModules JSI从0.76开始成为默认而Hermes与JSI是天然结合的——JSI让JS可以直接调用C层的对象比老架构的Bridge方式快得多。如果你的项目还没升级新架构建议走先开Hermes再切Fabric最后用TurboModules重写关键原生模块。这三步分开做每一步都好排查问题。新架构下一些老的桥接库会失效。升级前用npx react-native-tester扫描一下依赖把不兼容库找出来再决定升级策略。7. 我的最终建议oh-my-hermes该怎么用如果你现在还在用JSC或者刚开Hermes但一脸迷茫我的建议是把oh-my-hermes当作一份检查清单而不是一个黑盒工具。第一步照着配置文件检查你的工程确保Hermes开启且版本正确。第二步跑一遍性能基线脚本记录当前数据。第三步对照常见问题表逐个排查可能存在的坑。第四步把性能测试接入CI让退化在合并前就被挡住。这套流程走完后你的RN项目在启动速度、内存占用、流畅度上会有一个肉眼可见的提升。我自己在几个项目里验证过同样的代码启开Hermes并做完配套优化后低端Android机上卡顿反馈几乎消失用户评分也有明显回升。我在写oh-my-hermes的过程中最深的体会是性能优化没有银弹Hermes也不是万能药。它只是在引擎层面帮你把基础打好了真正的优化空间还是在你对业务代码结构的把握上。把引擎切换当成一次重构的契机而不是一次简单的开关翻转你才能获得最大的收益。
返回列表