ReactNative 0.60.4 Android 开启 Hermes

本贴最后更新于 1970 天前,其中的信息可能已经渤澥桑田

HermesHermes 引擎是 Facebook 研发,在 React-Native Android 端用于替换 JavaScript Core 的 JavaScript 引擎。Hermes 引擎的优势是适合移动端的轻量级 JavaScript 引擎,使用 aot 编译,可以减少 Android 端内存使用,减小安装包大小,提升执行效率。
相关文章:干货 | 加载速度提升 15%,携程对 RN 新一代 JS 引擎 Hermes 的调研
首先,请确保您至少使用 React Native 的 0.60.4 版本。

集成 Hermes

官网的集成方式很简单,只需要在 android/app/build.gradle 中将 enableHermes 改成 true,但基本上是不会成功的。下面是正确的开启方式~

  • 第一步 编辑 android/app/build.gradle 文件并进行如下所示的更改:
project.ext.react = [ entryFile: "index.js", enableHermes: true, // clean and rebuild if changing hermesCommand: "../../node_modules/hermes-engine/%OS-BIN%/hermes", ]
  • 第二步 npm install --save hermes-engine@0.1.0,安装 0.1.0 版本的 hermes

  • 第三步 替换 node_modules\react-native 目录下 react.gradle 文件

// Copyright (c) Facebook, Inc. and its affiliates. // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. import org.apache.tools.ant.taskdefs.condition.Os def config = project.hasProperty("react") ? project.react : []; def cliPath = config.cliPath ?: "node_modules/react-native/cli.js" def composeSourceMapsPath = config.composeSourceMapsPath ?: "node_modules/react-native/scripts/compose-source-maps.js" def bundleAssetName = config.bundleAssetName ?: "index.android.bundle" def entryFile = config.entryFile ?: "index.android.js" def bundleCommand = config.bundleCommand ?: "bundle" def reactRoot = file(config.root ?: "../../") def inputExcludes = config.inputExcludes ?: ["android/**", "ios/**"] def bundleConfig = config.bundleConfig ? "${reactRoot}/${config.bundleConfig}" : null ; def enableVmCleanup = config.enableVmCleanup == null ? true : config.enableVmCleanup def hermesCommand = config.hermesCommand ?: "../../node_modules/hermes-engine/%OS-BIN%/hermes" def reactNativeDevServerPort() { def value = project.getProperties().get("reactNativeDevServerPort") return value != null ? value : "8081" } def reactNativeInspectorProxyPort() { def value = project.getProperties().get("reactNativeInspectorProxyPort") return value != null ? value : reactNativeDevServerPort() } def getHermesOSBin() { if (Os.isFamily(Os.FAMILY_WINDOWS)) return "win64-bin"; if (Os.isFamily(Os.FAMILY_MAC)) return "osx-bin"; if (Os.isOs(null, "linux", "amd64", null)) return "linux64-bin"; throw new Exception("OS not recognized. Please set project.ext.react.hermesCommand " + "to the path of a working Hermes compiler."); } // Make sure not to inspect the Hermes config unless we need it, // to avoid breaking any JSC-only setups. def getHermesCommand = { // If the project specifies a Hermes command, don't second guess it. if (!hermesCommand.contains("%OS-BIN%")) { return hermesCommand } // Execution on Windows fails with / as separator return hermesCommand .replaceAll("%OS-BIN%", getHermesOSBin()) .replace('/' as char, File.separatorChar); } // Set enableHermesForVariant to a function to configure per variant, // or set `enableHermes` to True/False to set all of them def enableHermesForVariant = config.enableHermesForVariant ?: { def variant -> config.enableHermes ?: false } android { buildTypes.all { resValue "integer", "react_native_dev_server_port", reactNativeDevServerPort() resValue "integer", "react_native_inspector_proxy_port", reactNativeInspectorProxyPort() } } afterEvaluate { def isAndroidLibrary = plugins.hasPlugin("com.android.library") def variants = isAndroidLibrary ? android.libraryVariants : android.applicationVariants variants.all { def variant -> // Create variant and target names def targetName = variant.name.capitalize() def targetPath = variant.dirName // React js bundle directories def jsBundleDir = file("$buildDir/generated/assets/react/${targetPath}") def resourcesDir = file("$buildDir/generated/res/react/${targetPath}") def jsBundleFile = file("$jsBundleDir/$bundleAssetName") def jsSourceMapsDir = file("$buildDir/generated/sourcemaps/react/${targetPath}") def jsIntermediateSourceMapsDir = file("$buildDir/intermediates/sourcemaps/react/${targetPath}") def jsPackagerSourceMapFile = file("$jsIntermediateSourceMapsDir/${bundleAssetName}.packager.map") def jsCompilerSourceMapFile = file("$jsIntermediateSourceMapsDir/${bundleAssetName}.compiler.map") def jsOutputSourceMapFile = file("$jsSourceMapsDir/${bundleAssetName}.map") // Additional node and packager commandline arguments def nodeExecutableAndArgs = config.nodeExecutableAndArgs ?: ["node"] def extraPackagerArgs = config.extraPackagerArgs ?: [] def enableHermes = enableHermesForVariant(variant) def currentBundleTask = tasks.create( name: "bundle${targetName}JsAndAssets", type: Exec) { group = "react" description = "bundle JS and assets for ${targetName}." // Create dirs if they are not there (e.g. the "clean" task just ran) doFirst { jsBundleDir.deleteDir() jsBundleDir.mkdirs() resourcesDir.deleteDir() resourcesDir.mkdirs() jsIntermediateSourceMapsDir.deleteDir() jsIntermediateSourceMapsDir.mkdirs() jsSourceMapsDir.deleteDir() jsSourceMapsDir.mkdirs() } // Set up inputs and outputs so gradle can cache the result inputs.files fileTree(dir: reactRoot, excludes: inputExcludes) outputs.dir(jsBundleDir) outputs.dir(resourcesDir) // Set up the call to the react-native cli workingDir(reactRoot) // Set up dev mode def devEnabled = !(config."devDisabledIn${targetName}" || targetName.toLowerCase().contains("release")) def extraArgs = extraPackagerArgs; if (bundleConfig) { extraArgs = extraArgs.clone() extraArgs.add("--config"); extraArgs.add(bundleConfig); } if (Os.isFamily(Os.FAMILY_WINDOWS)) { commandLine("cmd", "/c", *nodeExecutableAndArgs, cliPath, bundleCommand, "--platform", "android", "--dev", "${devEnabled}", "--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir, "--sourcemap-output", enableHermes ? jsPackagerSourceMapFile : jsOutputSourceMapFile, *extraArgs) } else { commandLine(*nodeExecutableAndArgs, cliPath, bundleCommand, "--platform", "android", "--dev", "${devEnabled}", "--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir, "--sourcemap-output", enableHermes ? jsPackagerSourceMapFile : jsOutputSourceMapFile, *extraArgs) } if (enableHermes) { doLast { def hermesFlags; def hbcTempFile = file("${jsBundleFile}.hbc") exec { if (targetName.toLowerCase().contains("release")) { // Can't use ?: since that will also substitute valid empty lists hermesFlags = config.hermesFlagsRelease if (hermesFlags == null) hermesFlags = ["-O", "-output-source-map"] } else { hermesFlags = config.hermesFlagsDebug if (hermesFlags == null) hermesFlags = [] } if (Os.isFamily(Os.FAMILY_WINDOWS)) { commandLine("cmd", "/c", getHermesCommand(), "-emit-binary", "-out", hbcTempFile, jsBundleFile, *hermesFlags) } else { commandLine(getHermesCommand(), "-emit-binary", "-out", hbcTempFile, jsBundleFile, *hermesFlags) } } ant.move( file: hbcTempFile, toFile: jsBundleFile ); if (hermesFlags.contains("-output-source-map")) { ant.move( // Hermes will generate a source map with this exact name file: "${jsBundleFile}.hbc.map", tofile: jsCompilerSourceMapFile ); exec { // TODO: set task dependencies for caching // Set up the call to the compose-source-maps script workingDir(reactRoot) if (Os.isFamily(Os.FAMILY_WINDOWS)) { commandLine("cmd", "/c", *nodeExecutableAndArgs, composeSourceMapsPath, jsPackagerSourceMapFile, jsCompilerSourceMapFile, "-o", jsOutputSourceMapFile) } else { commandLine(*nodeExecutableAndArgs, composeSourceMapsPath, jsPackagerSourceMapFile, jsCompilerSourceMapFile, "-o", jsOutputSourceMapFile) } } } } } enabled config."bundleIn${targetName}" != null ? config."bundleIn${targetName}" : config."bundleIn${variant.buildType.name.capitalize()}" != null ? config."bundleIn${variant.buildType.name.capitalize()}" : targetName.toLowerCase().contains("release") } // Expose a minimal interface on the application variant and the task itself: variant.ext.bundleJsAndAssets = currentBundleTask currentBundleTask.ext.generatedResFolders = files(resourcesDir).builtBy(currentBundleTask) currentBundleTask.ext.generatedAssetsFolders = files(jsBundleDir).builtBy(currentBundleTask) // registerGeneratedResFolders for Android plugin 3.x if (variant.respondsTo("registerGeneratedResFolders")) { variant.registerGeneratedResFolders(currentBundleTask.generatedResFolders) } else { variant.registerResGeneratingTask(currentBundleTask) } variant.mergeResourcesProvider.get().dependsOn(currentBundleTask) // packageApplication for Android plugin 3.x def packageTask = variant.hasProperty("packageApplication") ? variant.packageApplicationProvider.get() : tasks.findByName("package${targetName}") if (variant.hasProperty("packageLibrary")) { packageTask = variant.packageLibrary } // pre bundle build task for Android plugin 3.2+ def buildPreBundleTask = tasks.findByName("build${targetName}PreBundle") def resourcesDirConfigValue = config."resourcesDir${targetName}" if (resourcesDirConfigValue) { def currentCopyResTask = tasks.create( name: "copy${targetName}BundledResources", type: Copy) { group = "react" description = "copy bundled resources into custom location for ${targetName}." from(resourcesDir) into(file(resourcesDirConfigValue)) dependsOn(currentBundleTask) enabled(currentBundleTask.enabled) } packageTask.dependsOn(currentCopyResTask) if (buildPreBundleTask != null) { buildPreBundleTask.dependsOn(currentCopyResTask) } } def currentAssetsCopyTask = tasks.create( name: "copy${targetName}BundledJs", type: Copy) { group = "react" description = "copy bundled JS into ${targetName}." if (config."jsBundleDir${targetName}") { from(jsBundleDir) into(file(config."jsBundleDir${targetName}")) } else { into ("$buildDir/intermediates") into ("assets/${targetPath}") { from(jsBundleDir) } // Workaround for Android Gradle Plugin 3.2+ new asset directory into ("merged_assets/${variant.name}/merge${targetName}Assets/out") { from(jsBundleDir) } // Workaround for Android Gradle Plugin 3.4+ new asset directory into ("merged_assets/${variant.name}/out") { from(jsBundleDir) } } // mergeAssets must run first, as it clears the intermediates directory dependsOn(variant.mergeAssetsProvider.get()) enabled(currentBundleTask.enabled) } packageTask.dependsOn(currentAssetsCopyTask) if (buildPreBundleTask != null) { buildPreBundleTask.dependsOn(currentAssetsCopyTask) } // Delete the VM related libraries that this build doesn't need. // The application can manage this manually by setting 'enableVmCleanup: false' // // This should really be done by packaging all Hermes releated libs into // two separate HermesDebug and HermesRelease AARs, but until then we'll // kludge it by deleting the .so files out of the /transforms/ directory. def isRelease = targetName.toLowerCase().contains("release") def libDir = "$buildDir/intermediates/transforms/" def vmSelectionAction = { fileTree(libDir).matching { if (enableHermes) { // For Hermes, delete all the libjsc* files include "**/libjsc*.so" if (isRelease) { // Reduce size by deleting the debugger/inspector include '**/libhermes-inspector.so' include '**/libhermes-executor-debug.so' } else { // Release libs take precedence and must be removed // to allow debugging include '**/libhermes-executor-release.so' } } else { // For JSC, delete all the libhermes* files include "**/libhermes*.so" } }.visit { details -> def targetVariant = ".*/transforms/[^/]*/${targetPath}/.*" def path = details.file.getAbsolutePath().replace(File.separatorChar, '/' as char) if (path.matches(targetVariant) && details.file.isFile()) { details.file.delete() } } } if (enableVmCleanup) { def task = tasks.findByName("package${targetName}") task.doFirst(vmSelectionAction) } } }

其实这一步是将 rn 默认调用的 hermesvm 改成 herms-engine,我们看下对比
image.png

image.png

  • 第四步 遵循官方提示,如果有修改 enableHermes,一定要在 android 下执行./gradlew clean 之后重新编译

修改之后基本可以运行打包了,如果遇到

More than one file was found with OS independent path 'lib/x86/libc++_shared.so'

这样的错,意思是有多个 lib/x86/libc++_shared.so 依赖,可以在 android/app/build.gradle 里的 android{} 配置项里,添加

packagingOptions { pickFirst '**/*.so' }

在编译时遇到相同的依赖,选择第一个。
或者直接声明

packagingOptions { pickFirst '**/x86/libc++_shared.so' }

开启 Hermes 后打出来包明显比没开启时小很多。不仅仅是 apk 体积变小了,而且启动速度也有明显提升,占用内存也明显下降。关于性能调研的对比可以参考这篇文章 RN 技术探索:Hermes Engine 初探
开启前:
image.png
image.png
image.png

开启后:
image.png
image.png
image.png

使用 Google Chrome 的 DevTools 调试 Hermes

Hermes 通过实现 Chrome 检查器协议来支持 Chrome 调试器。这意味着 Chrome 的工具可用于直接调试在模拟器或设备上的 Hermes 上运行的 JavaScript。

Chrome 通过 Metro 连接到在设备上运行的爱马仕,因此您需要知道 Metro 在哪里监听。通常,此功能将打开 localhost:8081,但这是可配置的。运行时 yarn start,启动时将地址写入 stdout。

知道 Metro 服务器正在侦听的位置后,您可以按照以下步骤连接 Chrome:

  1. chrome://inspect 在 Chrome 浏览器实例中导航到。

  2. 使用 Configure... 按钮添加 Metro 服务器地址(通常 localhost:8081 如上所述)。
    Chrome DevTools 设备页面中的“配置”按钮
    添加 Chrome DevTools 网络目标的对话框

3.现在,您应该看到带有“检查”链接的“ Hermes React Native”目标,可用于启动调试器。如果看不到“检查”链接,请确保 Metro 服务器正在运行。
目标检查链接
4.现在,您可以使用 Chrome 调试工具。例如,要在下次运行某些 JavaScript 时断点,请单击“暂停”按钮,然后在您的应用中触发一个将导致 JavaScript 执行的操作。

调试工具中的“暂停”按钮

  • React

    React 是 Facebook 开源的一个用于构建 UI 的 JavaScript 库。

    192 引用 • 291 回帖 • 385 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • 正则表达式

    正则表达式(Regular Expression)使用单个字符串来描述、匹配一系列遵循某个句法规则的字符串。

    31 引用 • 94 回帖 • 1 关注
  • TensorFlow

    TensorFlow 是一个采用数据流图(data flow graphs),用于数值计算的开源软件库。节点(Nodes)在图中表示数学操作,图中的线(edges)则表示在节点间相互联系的多维数据数组,即张量(tensor)。

    20 引用 • 19 回帖 • 2 关注
  • Gzip

    gzip (GNU zip)是 GNU 自由软件的文件压缩程序。我们在 Linux 中经常会用到后缀为 .gz 的文件,它们就是 Gzip 格式的。现今已经成为互联网上使用非常普遍的一种数据压缩格式,或者说一种文件格式。

    9 引用 • 12 回帖 • 169 关注
  • IDEA

    IDEA 全称 IntelliJ IDEA,是一款 Java 语言开发的集成环境,在业界被公认为最好的 Java 开发工具之一。IDEA 是 JetBrains 公司的产品,这家公司总部位于捷克共和国的首都布拉格,开发人员以严谨著称的东欧程序员为主。

    181 引用 • 400 回帖 • 3 关注
  • JRebel

    JRebel 是一款 Java 虚拟机插件,它使得 Java 程序员能在不进行重部署的情况下,即时看到代码的改变对一个应用程序带来的影响。

    26 引用 • 78 回帖 • 678 关注
  • 服务

    提供一个服务绝不仅仅是简单的把硬件和软件累加在一起,它包括了服务的可靠性、服务的标准化、以及对服务的监控、维护、技术支持等。

    41 引用 • 24 回帖
  • SOHO

    为成为自由职业者在家办公而努力吧!

    7 引用 • 55 回帖 • 4 关注
  • jQuery

    jQuery 是一套跨浏览器的 JavaScript 库,强化 HTML 与 JavaScript 之间的操作。由 John Resig 在 2006 年 1 月的 BarCamp NYC 上释出第一个版本。全球约有 28% 的网站使用 jQuery,是非常受欢迎的 JavaScript 库。

    63 引用 • 134 回帖 • 735 关注
  • 创造

    你创造的作品可能会帮助到很多人,如果是开源项目的话就更赞了!

    183 引用 • 1011 回帖 • 2 关注
  • 强迫症

    强迫症(OCD)属于焦虑障碍的一种类型,是一组以强迫思维和强迫行为为主要临床表现的神经精神疾病,其特点为有意识的强迫和反强迫并存,一些毫无意义、甚至违背自己意愿的想法或冲动反反复复侵入患者的日常生活。

    15 引用 • 161 回帖
  • Scala

    Scala 是一门多范式的编程语言,集成面向对象编程和函数式编程的各种特性。

    13 引用 • 11 回帖 • 159 关注
  • CAP

    CAP 指的是在一个分布式系统中, Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可兼得。

    12 引用 • 5 回帖 • 638 关注
  • C++

    C++ 是在 C 语言的基础上开发的一种通用编程语言,应用广泛。C++ 支持多种编程范式,面向对象编程、泛型编程和过程化编程。

    107 引用 • 153 回帖
  • GitHub

    GitHub 于 2008 年上线,目前,除了 Git 代码仓库托管及基本的 Web 管理界面以外,还提供了订阅、讨论组、文本渲染、在线文件编辑器、协作图谱(报表)、代码片段分享(Gist)等功能。正因为这些功能所提供的便利,又经过长期的积累,GitHub 的用户活跃度很高,在开源世界里享有深远的声望,并形成了社交化编程文化(Social Coding)。

    210 引用 • 2040 回帖
  • Angular

    AngularAngularJS 的新版本。

    26 引用 • 66 回帖 • 543 关注
  • webpack

    webpack 是一个用于前端开发的模块加载器和打包工具,它能把各种资源,例如 JS、CSS(less/sass)、图片等都作为模块来使用和处理。

    41 引用 • 130 回帖 • 250 关注
  • WebClipper

    Web Clipper 是一款浏览器剪藏扩展,它可以帮助你把网页内容剪藏到本地。

    3 引用 • 9 回帖 • 5 关注
  • 架构

    我们平时所说的“架构”主要是指软件架构,这是有关软件整体结构与组件的抽象描述,用于指导软件系统各个方面的设计。另外还有“业务架构”、“网络架构”、“硬件架构”等细分领域。

    143 引用 • 442 回帖
  • OpenCV
    15 引用 • 36 回帖 • 1 关注
  • Facebook

    Facebook 是一个联系朋友的社交工具。大家可以通过它和朋友、同事、同学以及周围的人保持互动交流,分享无限上传的图片,发布链接和视频,更可以增进对朋友的了解。

    4 引用 • 15 回帖 • 442 关注
  • 印象笔记
    3 引用 • 16 回帖
  • 招聘

    哪里都缺人,哪里都不缺人。

    189 引用 • 1057 回帖
  • Rust

    Rust 是一门赋予每个人构建可靠且高效软件能力的语言。Rust 由 Mozilla 开发,最早发布于 2014 年 9 月。

    58 引用 • 22 回帖
  • iOS

    iOS 是由苹果公司开发的移动操作系统,最早于 2007 年 1 月 9 日的 Macworld 大会上公布这个系统,最初是设计给 iPhone 使用的,后来陆续套用到 iPod touch、iPad 以及 Apple TV 等产品上。iOS 与苹果的 Mac OS X 操作系统一样,属于类 Unix 的商业操作系统。

    88 引用 • 139 回帖
  • 叶归
    5 引用 • 16 回帖 • 11 关注
  • Mobi.css

    Mobi.css is a lightweight, flexible CSS framework that focus on mobile.

    1 引用 • 6 回帖 • 755 关注
  • Firefox

    Mozilla Firefox 中文俗称“火狐”(正式缩写为 Fx 或 fx,非正式缩写为 FF),是一个开源的网页浏览器,使用 Gecko 排版引擎,支持多种操作系统,如 Windows、OSX 及 Linux 等。

    7 引用 • 30 回帖 • 391 关注