#前言 如今大部分混合應用使用了React Naitve,在Debug模式下隨着工程不斷的疊加龐大,加上自己工程沒有作組件化,使得每次工程編譯時間大大增長。 #1、Android React Native混合工程編譯分析 首先咱們切到工程的主Gradle文件,能夠發現主Gradle引入了React的編譯腳本以下所示java
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腳本里的內容node
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的就是下面的這段腳本代碼react
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任務裏執行的android
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以後調用ios
// Hook bundle${productFlavor}${buildType}JsAndAssets into the android build process
currentBundleTask.dependsOn("merge${targetName}Resources")
currentBundleTask.dependsOn("merge${targetName}Assets")
複製代碼
在這些Task以前被調用apache
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!!!
react-native
#2、優化代碼的實現 (1)拷貝react.gradle到Android 工程主模塊內,保持和主Gradle文件同級目錄,以下圖所示。 api
(2)修改主Gradle腳本project.ext.react = [
root : "../..",
bundleInDebug: true,
]
apply from: "react.gradle"
複製代碼
(3)新增isSkipBuildJsBundle方法app
/** * 註釋:是否跳過編譯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之內,能夠用很快來形容!!!