如今大部分混合應用使用了React Naitve,在Debug模式下隨着工程不斷的疊加龐大,加上自己工程沒有作組件化,使得每次工程編譯時間大大增長。java
首先咱們切到工程的主Gradle文件,能夠發現主Gradle引入了React的編譯腳本以下所示node
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腳本里的內容react
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的就是下面的這段腳本代碼android
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任務裏執行的ios
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) } .......... enabled isEnableBuildJsBundle(targetName) && (config."bundleIn${targetName}" || config."bundleIn${variant.buildType.name.capitalize()}" ?: targetName.toLowerCase().contains("release")) } 複製代碼
那麼咱們也就找到了優化的點,咱們能夠執行這個任務以前,打斷這個Task的執行,不去調用JsBundle的腳本,從而也就節省了JSBundle的掃描編譯時間,而這一部分的時間是至關可觀的,至少有2Min!!!apache
(1)拷貝react.gradle到Android 工程主模塊內,保持和主Gradle文件同級目錄,以下圖所示。 react-native
project.ext.react = [ root : "../..", bundleInDebug: true, ] apply from: "react.gradle" 複製代碼
(3)新增isEnableBuildJsBundle方法api
/** * 註釋:是否開啓編譯JsBundle任務 * 時間:2019/4/18 0018 10:18 * 做者:郭翰林 * @return */ boolean isEnableBuildJsBundle(String targetName) { if (targetName.toLowerCase().contains("release")) { return true } File jsBundle = file("$buildDir/intermediates/assets/debug/index.android.bundle") if (!jsBundle.exists()) { return true } else { println("【跳過編譯JsBundle】JsBundle已存在,無需再次編譯") return false } } 複製代碼
(4)改寫currentBundleTask()markdown
def currentBundleTask = tasks.create( name: "bundle${targetName}JsAndAssets", type: Exec) { group = "react" description = "bundle JS and assets for ${targetName}." ..................... 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 isEnableBuildJsBundle(targetName) && (config."bundleIn${targetName}" || config."bundleIn${variant.buildType.name.capitalize()}" ?: targetName.toLowerCase().contains("release")) } 複製代碼
最後看一下效果,Android工程增量編譯的時間,基本都在1Min之內,能夠用很快來形容!!! app