深度解析Gradle编译React native时遇到的那些坑【适用于Android开发者】

      React Native 出来已经有一年多的时间,到目前最新版0.39.0可谓是“惊喜”不断,这两天我就遇到了一个让我有点怀疑人生的“惊喜”,废话不多说,咱直接开始今天的脱坑之路——利用Gradle去编译React native

      首先咱来看看的rn给我们带来了哪些好东西,利用它我们可以做什么?

        react.gradle配置文件

import org.apache.tools.ant.taskdefs.condition.Os

def config = project.hasProperty("react") ? project.react : [];

def bundleAssetName = config.bundleAssetName ?: "index.android.bundle"
def entryFile = config.entryFile ?: "index.android.js"

// because elvis operator
def elvisFile(thing) {
    return thing ? file(thing) : null;
}

def reactRoot = elvisFile(config.root) ?: file("../../")
def inputExcludes = config.inputExcludes ?: ["android/**", "ios/**"]

void runBefore(String dependentTaskName, Task task) {
    Task dependentTask = tasks.findByPath(dependentTaskName);
    if (dependentTask != null) {
        dependentTask.dependsOn task
    }
}

gradle.projectsEvaluated {
    // Grab all build types and product flavors
    def buildTypes = android.buildTypes.collect { type -> type.name }
    def productFlavors = android.productFlavors.collect { flavor -> flavor.name }

    // When no product flavors defined, use empty
    if (!productFlavors) productFlavors.add('')

    productFlavors.each { productFlavorName ->
        buildTypes.each { buildTypeName ->
            // Create variant and target names
            def flavorNameCapitalized = "${productFlavorName.capitalize()}"
            def buildNameCapitalized = "${buildTypeName.capitalize()}"
            def targetName = "${flavorNameCapitalized}${buildNameCapitalized}"
            def targetPath = productFlavorName ?
                    "${productFlavorName}/${buildTypeName}" :
                    "${buildTypeName}"

            // React js bundle directories
            def jsBundleDirConfigName = "jsBundleDir${targetName}"
            def jsBundleDir = elvisFile(config."$jsBundleDirConfigName") ?:
                    file("$buildDir/intermediates/assets/${targetPath}")

            def resourcesDirConfigName = "resourcesDir${targetName}"
            def resourcesDir = elvisFile(config."${resourcesDirConfigName}") ?:
                    file("$buildDir/intermediates/res/merged/${targetPath}")
            def jsBundleFile = file("$jsBundleDir/$bundleAssetName")

            // Bundle task name for variant
            def bundleJsAndAssetsTaskName = "bundle${targetName}JsAndAssets"

            // Additional node and packager commandline arguments
            def nodeExecutableAndArgs = config.nodeExecutableAndArgs ?: ["node"]
            def extraPackagerArgs = config.extraPackagerArgs ?: []

            def currentBundleTask = tasks.create(
                    name: bundleJsAndAssetsTaskName,
                    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.mkdirs()
                    resourcesDir.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 = !targetName.toLowerCase().contains("release")
                if (Os.isFamily(Os.FAMILY_WINDOWS)) {
                    commandLine("cmd", "/c", *nodeExecutableAndArgs, "node_modules/react-native/local-cli/cli.js", "bundle", "--platform", "android", "--dev", "${devEnabled}",
                            "--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir, *extraPackagerArgs)
                } else {
                    commandLine(*nodeExecutableAndArgs, "node_modules/react-native/local-cli/cli.js", "bundle", "--platform", "android", "--dev", "${devEnabled}",
                            "--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir, *extraPackagerArgs)
                }

                enabled config."bundleIn${targetName}" ||
                    config."bundleIn${buildTypeName.capitalize()}" ?:
                            targetName.toLowerCase().contains("release")
            }

            // Hook bundle${productFlavor}${buildType}JsAndAssets into the android build process
            currentBundleTask.dependsOn("merge${targetName}Resources")
            currentBundleTask.dependsOn("merge${targetName}Assets")

            runBefore("process${flavorNameCapitalized}Armeabi-v7a${buildNameCapitalized}Resources", currentBundleTask)
            runBefore("process${flavorNameCapitalized}X86${buildNameCapitalized}Resources", currentBundleTask)
            runBefore("processUniversal${targetName}Resources", currentBundleTask)
            runBefore("process${targetName}Resources", currentBundleTask)
            runBefore("dataBindingProcessLayouts${targetName}", currentBundleTask)
        }
    }
}

看完这段代码之后,我们会发现两个有用的东西:

第一facebook为我们提供了自动化打包的配置,

第二解决了之前版本windows和mac打包需要执行不同脚本的bug

那么这个配置文件到底怎么用呢?接下来我们开始Gradle编译React native的配置。 

初始化一个React native项目打开android——app——build.gradle 官方默认会给你配置好这样一句代码【非常关键的一句代码】

apply from: "../../node_modules/react-native/react.gradle"

它的作用就是将上面react.gradle配置到你的android项目中,作用显而易见,其实添加完这句代码Gradle编译React native的配置基本上就算是完成了,这里我就不再详细介绍android集成React native的相关步骤了,网上一大推,其实重点我们要看看被facebook注释掉的这段配置

/**
 * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
 * and bundleReleaseJsAndAssets).
 * These basically call `react-native bundle` with the correct arguments during the Android build
 * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the
 * bundle directly from the development server. Below you can see all the possible configurations
 * and their defaults. If you decide to add a configuration block, make sure to add it before the
 * `apply from: "../../node_modules/react-native/react.gradle"` line.
 **/
 * project.ext.react = [
 *   // the name of the generated asset file containing your JS bundle
 *   bundleAssetName: "index.android.bundle",
 *
 *   // the entry file for bundle generation
 *   entryFile: "index.android.js",
 *
 *   // whether to bundle JS and assets in debug mode
 *   bundleInDebug: false,
 *
 *   // whether to bundle JS and assets in release mode
 *   bundleInRelease: true,
 *
 *   // whether to bundle JS and assets in another build variant (if configured).
 *   // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants
 *   // The configuration property can be in the following formats
 *   //         'bundleIn${productFlavor}${buildType}'
 *   //         'bundleIn${buildType}'
 *   // bundleInFreeDebug: true,
 *   // bundleInPaidRelease: true,
 *   // bundleInBeta: true,
 *
 *   // the root of your project, i.e. where "package.json" lives
 *   root: "../../",
 *
 *   // where to put the JS bundle asset in debug mode
 *   jsBundleDirDebug: "$buildDir/intermediates/assets/debug",
 *
 *   // where to put the JS bundle asset in release mode
 *   jsBundleDirRelease: "$buildDir/intermediates/assets/release",
 *
 *   // where to put drawable resources / React Native assets, e.g. the ones you use via
 *   // require('./image.png')), in debug mode
 *   resourcesDirDebug: "$buildDir/intermediates/res/merged/debug",
 *
 *   // where to put drawable resources / React Native assets, e.g. the ones you use via
 *   // require('./image.png')), in release mode
 *   resourcesDirRelease: "$buildDir/intermediates/res/merged/release",
 *
 *   // by default the gradle tasks are skipped if none of the JS files or assets change; this means
 *   // that we don't look at files in android/ or ios/ to determine whether the tasks are up to
 *   // date; if you have any other folders that you want to ignore for performance reasons (gradle
 *   // indexes the entire tree), add them here. Alternatively, if you have JS files in android/
 *   // for example, you might want to remove it from here.
 *   inputExcludes: ["android/**", "ios/**"],
 *
 *   // override which node gets called and with what additional arguments
 *   nodeExecutableAndArgs: ["node"]
 *
 *   // supply additional arguments to the packager
 *   extraPackagerArgs: []
 * ]
 */

       对比最开始我给大家展示的react.gradle代码可以看出,gradle编译React native 是非常灵活的,facebook给大家提供了编译模式的配置,输出路径的配置,平常我们较为常用的便是root的配置

the root of your project, i.e. where "package.json" lives

到此所有的配置已经完成,是不是觉的很简单

       开启填坑模式...

       坑一:如果你的gradle输出路径不是默认的路径,那么你打出的release包可能会没有包含jsbundle,解决办法修改你的android gradle输出路径为默认的路径,与rn的保持一致,

      坑二:Execution failed for task ':app:bundleReleaseJsAndAssets'.
                 > A problem occurred starting process 'command 'node'  说实话这个问题困扰我的时间最长,后来在github看到了别人给React native提的一个issues才找到了答案,解决办法,命令行先执行 ./gradlew stop然后执行 ./gradlew assembleRelease

      坑三:win创建的android 项目集成了React native Mac通过git获取下来,却发现./gradlew  command not found 怎么办呢?也有两种方法修改1.设置fileformat=unix【不建议】2.重新替换你的gradlew和gradlew.bat

      坑四:如果是原生android集成的React native 请注意 https://github.com/facebook/react-native/issues/5787

      关闭填坑模式...

最后希望大家在学习React native的路上少遇到一些坑

第一次写,还是有点不适应,希望大家有愿意一起学习的小伙伴加我的qq群581621024 一起来探讨 


  


 


你可能感兴趣的:(深度解析Gradle编译React native时遇到的那些坑【适用于Android开发者】)