Android 优化React Native在Debug环境下的编译时间

#前言 现在大部分混合应用使用了React Naitve,在Debug模式下随着工程不断的叠加庞大,加上本身工程没有做组件化,使得每次工程编译时间大大增加。 #一、Android React Native混合工程编译分析 首先我们切到工程的主Gradle文件,可以发现主Gradle引入了React的编译脚本如下所示

project.ext.react = [
        root         : "../..",
        bundleInDebug: true,
]
apply from: "../../node_modules/react-native/react.gradle"
复制代码

bundleInDebug默认为false,但是大部分工程首页一般都会有引用RN的部分页面,如果这里不设置为true的话,进入首页会引起首页黑屏。所以一般这边的值都会设置为true。而这个也是导致工程编译很慢的罪魁祸首。那有没有什么办法可以保证,Android在Debug模式下即可以把JsBundle打进App应用内,而且在平时开发调试中页会很快的编译的完成呢? 答案是一定可以的,我们接着去查看主Gradle引用的react.gradle脚本里的内容

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

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

def cliPath = config.cliPath ?: "node_modules/react-native/local-cli/cli.js"
def bundleAssetName = config.bundleAssetName ?: "index.android.bundle"
def entryFile = config.entryFile ?: "index.android.js"
def bundleCommand = config.bundleCommand ?: "bundle"

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

def reactRoot = elvisFile(config.root) ?: file("../../")
def inputExcludes = config.inputExcludes ?: ["android/**", "ios/**"]
def bundleConfig = config.bundleConfig ? "${reactRoot}/${config.bundleConfig}" : null ;

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 = !(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, *extraArgs)
                } else {
                    commandLine(*nodeExecutableAndArgs, cliPath, bundleCommand, "--platform", "android", "--dev", "${devEnabled}",
                            "--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir, *extraArgs)
                }

                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)
        }
    }
}

复制代码

阅读以上gradle脚本可以发现,主要执行打包JsBundle的就是下面的这段脚本代码

    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, *extraArgs)
    } else {
        commandLine(*nodeExecutableAndArgs, cliPath, bundleCommand, "--platform", "android", "--dev", "${devEnabled}",
        "--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir, *extraArgs)
    }

复制代码

而这段脚本是在被动态创建的Task任务里执行的

   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()
                }
                ......
                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, *extraArgs)
                } else {
                    commandLine(*nodeExecutableAndArgs, cliPath, bundleCommand, "--platform", "android", "--dev", "${devEnabled}",
                            "--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir, *extraArgs)
                }
   }
复制代码

这个动态创建的任务被在这些Task之后调用

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

在这些Task之前被调用

            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)
复制代码

那么我们也就找到了优化的点,我们可以在上述这些Task之前,打断这个Task的执行,不去调用JsBundle的脚本,从而也就节省了JSBundle的扫描编译时间,而这一部分的时间是相当可观的,至少有2Min!!!

#二、优化代码的实现 (1)拷贝react.gradle到Android 工程主模块内,保持和主Gradle文件同级目录,如下图所示。

(2)修改主Gradle脚本

project.ext.react = [
        root         : "../..",
        bundleInDebug: true,
]
apply from: "react.gradle"
复制代码

(3)新增isSkipBuildJsBundle方法

/**
 * 注释:是否跳过编译JsBundle
 * 时间:2019/4/18 0018 10:18
 * 作者:郭翰林
 * @return
 */
boolean isSkipBuildJsBundle(String targetName) {
    if (!targetName.contains("Debug")) {
        return false
    }
    File jsBundle = file("build/intermediates/assets/debug/index.android.bundle")
    if (!jsBundle.exists()) {
        return false
    } else {
        println("【跳过编译JsBundle】JsBundle已存在,无需再次编译")
        return true
    }
}
复制代码

(4)改写runBefore()

void runBefore(String dependentTaskName, Task task) {
    Task dependentTask = tasks.findByPath(dependentTaskName);
    String taskName = task.getName()
    if (dependentTask != null && !isSkipBuildJsBundle(taskName)) {
        dependentTask.dependsOn task
    }
}
复制代码

最后看一下效果,Android工程增量编译的时间,基本都在1Min以内,可以用很快来形容!!!

转载于:https://juejin.im/post/5cbd8c516fb9a0322758ba5e

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值