前一段时间由于业务需要,接触了下React Native相关的知识,以一个Android开发者的视角,对React Native 项目组织和构建流程有了一些粗浅的认识,同时也对RN混合开发项目如何搭建又了一点小小的思考。
RN环境搭建
RN文档提供了两种搭建RN环境的方式
- 搭建开发环境 创建纯RN项目
- 把RN集成到已有项目
文档写的也比较清晰,按照步骤做就可以。
默认项目结构分析
按照文档 https://reactnative.cn/docs/environment-setup 创建好项目后,我们来分析下目录结构
根目录就是一个标准的RN项目,其中有一个node_modules
目录,该目录是项目的依赖包。
根项目目录下有一个android
目录和一个ios
目录,分别是Android和iOS的原生代码目录,也就是说,android和ios项目是作为RN项目的子项目存在的。
来看下android目录中的代码,这个目录下的代码是一个标准的Android项目,直接使用Android Studio打开即可。
可以看到,除了一个标准的Android项目外,还有一个gradle-plugin的。
下面是 settings.gradle
文件的内容
在settings.gradle
中,应用了一个叫做 native_modules.gradle 的脚本
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
还通过includeBuild引入了一个RN插件
includeBuild('../node_modules/@react-native/gradle-plugin')
再来接着看看根目录下build.gradle文件中的内容
buildscript {
ext {
buildToolsVersion = "34.0.0"
minSdkVersion = 21
compileSdkVersion = 34
targetSdkVersion = 34
ndkVersion = "25.1.8937393"
kotlinVersion = "1.8.0"
}
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle")
//RN插件
classpath("com.facebook.react:react-native-gradle-plugin")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin")
}
}
//应用了一个叫做com.facebook.react.rootproject的插件
apply plugin: "com.facebook.react.rootproject"
接着看下app目录下的build.gradle文件
apply plugin: "com.android.application"
apply plugin: "org.jetbrains.kotlin.android"
//应用了一个叫做com.facebook.react的插件
apply plugin: "com.facebook.react"
/**
* This is the configuration block to customize your React Native Android app.
* By default you don't need to apply any configuration, just uncomment the lines you need.
*/
react {
/* Folders */
// The root of your project, i.e. where "package.json" lives. Default is '..'
// root = file("../")
// The folder where the react-native NPM package is. Default is ../node_modules/react-native
// reactNativeDir = file("../node_modules/react-native")
// The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen
// codegenDir = file("../node_modules/@react-native/codegen")
// The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js
// cliFile = file("../node_modules/react-native/cli.js")
/* Variants */
// The list of variants to that are debuggable. For those we're going to
// skip the bundling of the JS bundle and the assets. By default is just 'debug'.
// If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.
// debuggableVariants = ["liteDebug", "prodDebug"]
/* Bundling */
// A list containing the node command and its flags. Default is just 'node'.
// nodeExecutableAndArgs = ["node"]
//
// The command to run when bundling. By default is 'bundle'
// bundleCommand = "ram-bundle"
//
// The path to the CLI configuration file. Default is empty.
// bundleConfig = file(../rn-cli.config.js)
//
// The name of the generated asset file containing your JS bundle
// bundleAssetName = "MyApplication.android.bundle"
//
// The entry file for bundle generation. Default is 'index.android.js' or 'index.js'
// entryFile = file("../js/MyApplication.android.js")
//
// A list of extra flags to pass to the 'bundle' commands.
// See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle
// extraPackagerArgs = []
/* Hermes Commands */
// The hermes compiler command to run. By default it is 'hermesc'
// hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc"
//
// The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
// hermesFlags = ["-O", "-output-source-map"]
}
/**
* Set this to true to Run Proguard on Release builds to minify the Java bytecode.
*/
def enableProguardInReleaseBuilds = false
/**
* The preferred build flavor of JavaScriptCore (JSC)
*
* For example, to use the international variant, you can use:
* `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
*
* The international variant includes ICU i18n library and necessary data
* allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
* give correct results when using with locales other than en-US. Note that
* this variant is about 6MiB larger per architecture than default.
*/
def jscFlavor = 'org.webkit:android-jsc:+'
android {
ndkVersion rootProject.ext.ndkVersion
compileSdk rootProject.ext.compileSdkVersion
namespace "com.yzq.rn_project_analysis"
defaultConfig {
applicationId "com.yzq.rn_project_analysis"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
}
signingConfigs {
debug {
storeFile file('debug.keystore')
storePassword 'android'
keyAlias 'androiddebugkey'
keyPassword 'android'
}
}
buildTypes {
debug {
signingConfig signingConfigs.debug
}
release {
// Caution! In production, you need to generate your own keystore file.
// see https://reactnative.dev/docs/signed-apk-android.
signingConfig signingConfigs.debug
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
}
dependencies {
// The version of react-native is set by the React Native Gradle Plugin
implementation("com.facebook.react:react-android")
implementation("com.facebook.react:flipper-integration")
if (hermesEnabled.toBoolean()) {
implementation("com.facebook.react:hermes-android")
} else {
implementation jscFlavor
}
}
//应用了一个脚本文件
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
可以看到,工程的依赖配置也比较的清晰,主要是配置了一些Android的基本配置,然后应用了RN的插件和脚本。
三方库使用
三方库在RN中有着非常重要的地位,因为RN本身的功能是有限的,所以需要依赖一些三方库来实现一些功能。
三方库一般提供了跨平台的支持,对前端开发同学来讲是非常友好的,不需要去了解原生的开发技术,就可以实现一些原生的功能。
三方库的使用方式非常简单,按照使用三方库文档来就可以了。
下面随便去 https://reactnative.directory/ 找一个三方库来简单使用一下看看。
就以 react-native-device-info 为例吧
在项目根目录下执行下面命令安装即可
yarn add react-native-device-info
安装完成后会发现,项目根目录下的package.json文件中多了一条依赖
然后在RN项目中使用一下
import DeviceInfo from "react-native-device-info";
<Button title={"deviceInfo"} onPress={() => {
DeviceInfo.getAndroidId().then((id) => {
console.log(id);
})
}}/>
然后重新运行项目, 点击按钮,就可以看到控制台输出了设备的id
使用起来非常简单,可以看到,这里实际上完全不需要关心native端的代码,就可以实现一些原生的功能。
那作为 native 端开发的同学,这里不免就会好奇一个问题:
正常来讲如果我们在原生项目中使用三方库,是需要引入三方库的jar包或者aar包的,大部分sdk还需要进行初始化操作,然后才能调用相关的方法,
只需要一个yarn add react-native-device-info
就能让RN项目使用原生的功能,这是怎么做到的呢?
带着这个问题,先来看看Android项目有什么变化。
这个module是怎么引入的呢?正常来讲在Android中我们想要引入一个本地的module,需要在settings.gradle中include进来,然后在build.gradle中引入依赖。
但是,再次去看看settings.gradle和build.gradle文件,发现并没有类似的代码,那这个module是怎么引入的呢?
还记得上面在分析项目结构的时候,我们提到的一个脚本和一个插件吗?
- apply from: file(“…/node_modules/@react-native-community/cli-platform-android/native_modules.gradle”);
- includeBuild(‘…/node_modules/@react-native/gradle-plugin’)
实际上,这两个东西就是管理RN Android项目的配置和依赖的,是非常重要的角色。
react-native-gradle-plugin 分析
我们先来分析一下react-native-gradle-plugin
这个插件,这个插件是RN项目的核心插件,它的作用是管理RN项目的依赖和配置。
通过源码配置可以看到,一共提供了两个插件
- com.facebook.react
- com.facebook.react.rootproject
com.facebook.react.rootproject
我们先来看看 com.facebook.react.rootproject
该插件在项目的根目录下的build.gradle文件中被应用了
/**
* 该插件应用于`android/build.gradle`文件。
* 该插件的作用是确保app项目在库项目之前被配置,以便在库项目被配置时可以使用app项目的配置
*
* @constructor
*/
class ReactRootProjectPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.subprojects {
// As the :app project (i.e. ReactPlugin) configures both namespaces and JVM toolchains
// for libraries, its evaluation must happen before the libraries' evaluation.
// Eventually the configuration of namespace/JVM toolchain can be moved inside this plugin.
if (it.path != ":app") {
it.evaluationDependsOn(":app")
}
}
}
}
代码非常少,其作用就是是确保app项目在库项目之前被配置,以便在库项目被配置时可以使用app项目的配置。
简单说就是app中会有一些rn相关的配置,一些三方库中也会用到这些配置,此时需要确保app项目的配置在库项目之前被配置,以确保其他模块能够正常使用。
com.facebook.react
该插件是在app项目的build.gradle文件中被应用了
这个插件的代码相对多一些,我们来一点一点的分析
override fun apply(project: Project) {
//检查JVM版本,不能低于17
checkJvmVersion(project)
//创建react配置
val extension = project.extensions.create("react", ReactExtension::class.java, project)
// We register a private extension on the rootProject so that project wide configs
// like codegen config can be propagated from app project to libraries.
/**
* 在根项目创建一个私有的配置项 privateReact,如果已经存在则获取
* 用于在app项目和库项目之间共享配置
*/
val rootExtension =
project.rootProject.extensions.findByType(PrivateReactExtension::class.java)
?: project.rootProject.extensions.create(
"privateReact", PrivateReactExtension::class.java, project
)
// App Only Configuration
/**
* 如果项目中使用了com.android.application插件,也就是app模块中会执行以下代码
*/
project.pluginManager.withPlugin("com.android.application") {
// We wire the root extension with the values coming from the app (either user populated or
// defaults).
/**
* 下面代码实际上就是把用户自定义的配置赋值给rootExtension,就是把用户自定义的配置传递给上面创建好的一个私有配置项 privateReact
*/
rootExtension.root.set(extension.root)
rootExtension.reactNativeDir.set(extension.reactNativeDir)
rootExtension.codegenDir.set(extension.codegenDir)
rootExtension.nodeExecutableAndArgs.set(extension.nodeExecutableAndArgs)
println("rootExtension root: ${rootExtension.root.get()}")
println("rootExtension reactNativeDir: ${rootExtension.reactNativeDir.get()}")
println("rootExtension codegenDir: ${rootExtension.codegenDir.get()}")
println("rootExtension nodeExecutableAndArgs: ${rootExtension.nodeExecutableAndArgs.get()}")
/**
* 项目配置完成后,执行以下代码
*/
project.afterEvaluate {
val reactNativeDir = extension.reactNativeDir.get().asFile
val propertiesFile = File(reactNativeDir, "ReactAndroid/gradle.properties")
//获取版本号和groupName
val versionAndGroupStrings = readVersionAndGroupStrings(propertiesFile)
val versionString = versionAndGroupStrings.first
val groupString = versionAndGroupStrings.second
//配置依赖,主要是做了依赖替换和统一版本的逻辑
configureDependencies(project, versionString, groupString)
//配置仓库
configureRepositories(project, reactNativeDir)
}
//配置NDK
configureReactNativeNdk(project, extension)
//配置App的构建配置字段
configureBuildConfigFieldsForApp(project, extension)
//配置开发端口 默认8081
configureDevPorts(project)
//处理老版本配置兼容性
configureBackwardCompatibilityReactMap(project)
//配置Java工具链,确保项目中的 Java 和 Kotlin 代码使用 Java 17 版本
configureJavaToolChains(project)
//根据不同的构建类型配置不同的任务
project.extensions.getByType(AndroidComponentsExtension::class.java).apply {
onVariants(selector().all()) { variant ->
//配置react任务,用于执行react-native的打包操作
project.configureReactTasks(variant = variant, config = extension)
}
}
//配置react-native-codegen,用于生成所需代码
configureCodegen(project, extension, rootExtension, isLibrary = false)
}
// Library Only Configuration
configureBuildConfigFieldsForLibraries(project)
configureNamespaceForLibraries(project)
project.pluginManager.withPlugin("com.android.library") {
configureCodegen(project, extension, rootExtension, isLibrary = true)
}
}
插件主要有以下逻辑
- 检查JVM版本,不能低于17
private fun checkJvmVersion(project: Project) {
val jvmVersion = Jvm.current()?.javaVersion?.majorVersion
println("jvmVersion: $jvmVersion")
if ((jvmVersion?.toIntOrNull() ?: 0) <= 16) {
project.logger.error(
"""
********************************************************************************
ERROR: requires JDK17 or higher.
Incompatible major version detected: '$jvmVersion'
********************************************************************************
"""
.trimIndent()
)
exitProcess(1)
}
}
- 创建react配置
val extension = project.extensions.create("react", ReactExtension::class.java, project)
- 在根项目创建一个私有的配置项 privateReact,如果已经存在则获取,用于在app项目和库项目之间共享配置
val rootExtension =
project.rootProject.extensions.findByType(PrivateReactExtension::class.java)
?: project.rootProject.extensions.create(
"privateReact", PrivateReactExtension::class.java, project
)
PrivateReactExtension 的代码如下
abstract class PrivateReactExtension @Inject constructor(project: Project) {
private val objects = project.objects
/**
* 创建一个根目录的属性
* 最终的值根据项目名称决定
* 如果项目名称为"react-native-github"或"react-native-build-from-source",则目录为"../../"
* 如果项目名称为其他,则目录为"../"
*/
val root: DirectoryProperty = objects.directoryProperty().convention(
if (project.rootProject.name == "react-native-github" || project.rootProject.name == "react-native-build-from-source") {
project.rootProject.layout.projectDirectory.dir("../../")
} else {
project.rootProject.layout.projectDirectory.dir("../")
}
)
/**
* reactNativeDir的默认值为"node_modules/react-native"
*/
val reactNativeDir: DirectoryProperty =
objects.directoryProperty().convention(root.dir("node_modules/react-native"))
/**
* 指定 Node.js 可执行文件及其运行时参数,默认就是node,一般不会改
*/
val nodeExecutableAndArgs: ListProperty<String> =
objects.listProperty(String::class.java).convention(listOf("node"))
/**
* 生成代码的目录
*/
val codegenDir: DirectoryProperty =
objects.directoryProperty().convention(root.dir("node_modules/@react-native/codegen"))
}
- 如果项目中使用了com.android.application插件,也就是app模块中会执行以下代码
-
用户自定义的配置赋值给rootExtension,就是把用户自定义的配置传递给上面创建好的一个私有配置项 privateReact
project.pluginManager.withPlugin("com.android.application") { // We wire the root extension with the values coming from the app (either user populated or // defaults). rootExtension.root.set(extension.root) rootExtension.reactNativeDir.set(extension.reactNativeDir) rootExtension.codegenDir.set(extension.codegenDir) rootExtension.nodeExecutableAndArgs.set(extension.nodeExecutableAndArgs) }
-
配置依赖,主要是做了依赖替换和统一版本的逻辑,这也就是为什么在app的build.gradle中的react
native相关的依赖没有指定版本,实际上是在这里统一配置的val reactNativeDir = extension.reactNativeDir.get().asFile val propertiesFile = File(reactNativeDir, "ReactAndroid/gradle.properties") //获取版本号和groupName val versionAndGroupStrings = readVersionAndGroupStrings(propertiesFile) val versionString = versionAndGroupStrings.first val groupString = versionAndGroupStrings.second //配置依赖,主要是做了依赖替换和统一版本的逻辑 configureDependencies(project, versionString, groupString)
readVersionAndGroupStrings方法,实际上就是从/node_modules/reactnative/ReactAndroid/gradle.properties文件中读取版本号和group字符串
/** * 读取版本和group字符串 * @param propertiesFile File * @return Pair<String, String> */ fun readVersionAndGroupStrings(propertiesFile: File): Pair<String, String> { println("readVersionAndGroupStrings: $propertiesFile") val reactAndroidProperties = Properties() propertiesFile.inputStream().use { reactAndroidProperties.load(it) } val versionStringFromFile = reactAndroidProperties[INTERNAL_VERSION_NAME] as? String ?: "" // If on a nightly, we need to fetch the -SNAPSHOT artifact from Sonatype. val versionString = if (versionStringFromFile.startsWith("0.0.0") || "-nightly-" in versionStringFromFile) { "$versionStringFromFile-SNAPSHOT" } else { versionStringFromFile } // Returns Maven group for repos using different group for Maven artifacts val groupString = reactAndroidProperties[INTERNAL_PUBLISHING_GROUP] as? String ?: DEFAULT_INTERNAL_PUBLISHING_GROUP return Pair(versionString, groupString) }
configureDependencies方法,主要做了依赖替换和统一版本的逻辑
/** * 配置依赖 * 1.替换依赖 * 2.强制使用指定版本 * * @param project Project * @param versionString String * @param groupString String */ fun configureDependencies( project: Project, versionString: String, groupString: String = DEFAULT_INTERNAL_PUBLISHING_GROUP ) { println("configureDependencies: $versionString, $groupString") if (versionString.isBlank()) return //遍历所有项目 project.rootProject.allprojects { eachProject -> println("eachProject: ${eachProject.name}") //遍历项目的所有配置 eachProject.configurations.all { configuration -> /** * configuration.resolutionStrategy 用于配置解析策略,一般用于配置依赖替换和强制使用指定版本 */ configuration.resolutionStrategy.dependencySubstitution { //获取依赖替换列表 getDependencySubstitutions( versionString, groupString ).forEach { (module, dest, reason) -> //将指定的依赖替换为目标依赖 it.substitute(it.module(module)).using(it.module(dest)).because(reason) } } //强制使用指定版本 configuration.resolutionStrategy.force( "${groupString}:react-android:${versionString}", "${groupString}:flipper-integration:${versionString}", ) //如果用户没有选择使用夜间版本进行本地开发,则强制使用hermes-android指定版本 if (!(eachProject.findProperty(INTERNAL_USE_HERMES_NIGHTLY) as? String).toBoolean()) { // Contributors only: The hermes-engine version is forced only if the user has // not opted into using nightlies for local development. configuration.resolutionStrategy.force("${groupString}:hermes-android:${versionString}") } } } }
getDependencySubstitutions方法,主要是生成需要进行依赖替换的列表
/** * 生成依赖替换列表 * @param versionString String * @param groupString String * @return List<Triple<String, String, String>> */ internal fun getDependencySubstitutions( versionString: String, groupString: String = DEFAULT_INTERNAL_PUBLISHING_GROUP ): List<Triple<String, String, String>> { /** * 生成依赖替换列表 * first:原始依赖 * second:替换后的依赖 * third:原因 */ val dependencySubstitution = mutableListOf<Triple<String, String, String>>() // react-native替换为react-android dependencySubstitution.add( Triple( "com.facebook.react:react-native", "${groupString}:react-android:${versionString}", "The react-native artifact was deprecated in favor of react-android due to https://github.com/facebook/react-native/issues/35210." ) ) // hermes-engine替换为hermes-android dependencySubstitution.add( Triple( "com.facebook.react:hermes-engine", "${groupString}:hermes-android:${versionString}", "The hermes-engine artifact was deprecated in favor of hermes-android due to https://github.com/facebook/react-native/issues/35210." ) ) // 如果 groupString 不是默认值 com.facebook.react,则修改react-android和hermes-android的Maven group if (groupString != DEFAULT_INTERNAL_PUBLISHING_GROUP) { dependencySubstitution.add( Triple( "com.facebook.react:react-android", "${groupString}:react-android:${versionString}", "The react-android dependency was modified to use the correct Maven group." ) ) dependencySubstitution.add( Triple( "com.facebook.react:hermes-android", "${groupString}:hermes-android:${versionString}", "The hermes-android dependency was modified to use the correct Maven group." ) ) } return dependencySubstitution }
-
配置仓库源,这个比较简单,就是配置了一些依赖所需的仓库地址
fun configureRepositories(project: Project, reactNativeDir: File) { println("configureRepositories: $reactNativeDir") project.rootProject.allprojects { eachProject -> with(eachProject) { if (hasProperty(INTERNAL_REACT_NATIVE_MAVEN_LOCAL_REPO)) { val mavenLocalRepoPath = property(INTERNAL_REACT_NATIVE_MAVEN_LOCAL_REPO) as String mavenRepoFromURI(File(mavenLocalRepoPath).toURI()) } // We add the snapshot for users on nightlies. mavenRepoFromUrl("https://oss.sonatype.org/content/repositories/snapshots/") repositories.mavenCentral { repo -> // We don't want to fetch JSC from Maven Central as there are older versions there. repo.content { it.excludeModule("org.webkit", "android-jsc") } } // Android JSC is installed from npm mavenRepoFromURI(File(reactNativeDir, "../jsc-android/dist").toURI()) repositories.google() mavenRepoFromUrl("https://www.jitpack.io") } } }
-
配置NDK(Native Development Kit)相关设置
fun configureReactNativeNdk(project: Project, extension: ReactExtension) { project.pluginManager.withPlugin("com.android.application") { project.extensions.getByType(AndroidComponentsExtension::class.java) .finalizeDsl { ext -> //是否启用新架构 没有直接返回 if (!project.isNewArchEnabled(extension)) { // For Old Arch, we don't need to setup the NDK return@finalizeDsl } // We enable prefab so users can consume .so/headers from ReactAndroid and hermes-engine // .aar ext.buildFeatures.prefab = true // If the user has not provided a CmakeLists.txt path, let's provide // the default one from the framework if (ext.externalNativeBuild.cmake.path == null) { ext.externalNativeBuild.cmake.path = File( extension.reactNativeDir.get().asFile, "ReactAndroid/cmake-utils/default-app-setup/CMakeLists.txt" ) } // Parameters should be provided in an additive manner (do not override what // the user provided, but allow for sensible defaults). val cmakeArgs = ext.defaultConfig.externalNativeBuild.cmake.arguments if (cmakeArgs.none { it.startsWith("-DPROJECT_BUILD_DIR") }) { cmakeArgs.add("-DPROJECT_BUILD_DIR=${project.layout.buildDirectory.get().asFile}") } if (cmakeArgs.none { it.startsWith("-DREACT_ANDROID_DIR") }) { cmakeArgs.add( "-DREACT_ANDROID_DIR=${ extension.reactNativeDir.file("ReactAndroid").get().asFile }" ) } if (cmakeArgs.none { it.startsWith("-DANDROID_STL") }) { cmakeArgs.add("-DANDROID_STL=c++_shared") } // Due to the new NDK toolchain file, the C++ flags gets overridden between compilation // units. This is causing some libraries to don't be compiled with -DANDROID and other // crucial flags. This can be revisited once we bump to NDK 25/26 if (cmakeArgs.none { it.startsWith("-DANDROID_USE_LEGACY_TOOLCHAIN_FILE") }) { cmakeArgs.add("-DANDROID_USE_LEGACY_TOOLCHAIN_FILE=ON") } val architectures = project.getReactNativeArchitectures() // abiFilters are split ABI are not compatible each other, so we set the abiFilters // only if the user hasn't enabled the split abi feature. if (architectures.isNotEmpty() && !ext.splits.abi.isEnable) { ext.defaultConfig.ndk.abiFilters.addAll(architectures) } } } }
-
配置App的构建配置字段
/** * 确保在 Android 应用或库项目中启用buildConfig,并添加了两个自定义的布尔类型的构建配置字段,用于表示新架构是否启用以及是否启用了 Hermes 引擎。 * 这些字段将在生成的 BuildConfig 类中作为静态字段提供。 * @param project Project * @param extension ReactExtension */ fun configureBuildConfigFieldsForApp(project: Project, extension: ReactExtension) { val action = Action<AppliedPlugin> { project.extensions.getByType(AndroidComponentsExtension::class.java) .finalizeDsl { ext -> ext.buildFeatures.buildConfig = true ext.defaultConfig.buildConfigField( "boolean", "IS_NEW_ARCHITECTURE_ENABLED", project.isNewArchEnabled(extension).toString() ) ext.defaultConfig.buildConfigField( "boolean", "IS_HERMES_ENABLED", project.isHermesEnabled.toString() ) } } project.pluginManager.withPlugin("com.android.application", action) project.pluginManager.withPlugin("com.android.library", action) }
-
配置开发端口 默认8081
fun configureDevPorts(project: Project) { val devServerPort = project.properties["reactNativeDevServerPort"]?.toString() ?: DEFAULT_DEV_SERVER_PORT val inspectorProxyPort = project.properties["reactNativeInspectorProxyPort"]?.toString() ?: devServerPort val action = Action<AppliedPlugin> { project.extensions.getByType(AndroidComponentsExtension::class.java) .finalizeDsl { ext -> ext.defaultConfig.resValue( "integer", "react_native_dev_server_port", devServerPort ) ext.defaultConfig.resValue( "integer", "react_native_inspector_proxy_port", inspectorProxyPort ) } } project.pluginManager.withPlugin("com.android.application", action) project.pluginManager.withPlugin("com.android.library", action) }
-
处理老版本配置兼容性
fun configureBackwardCompatibilityReactMap(project: Project) { if (project.extensions.extraProperties.has("react")) { @Suppress("UNCHECKED_CAST") val reactMap = project.extensions.extraProperties.get("react") as? Map<String, Any?> ?: mapOf() if (reactMap.isNotEmpty()) { project.logger.error( """ ******************************************************************************** ERROR: Using old project.ext.react configuration. We identified that your project is using a old configuration block as: project.ext.react = [ // ... ] You should migrate to the new configuration: react { // ... } You can find documentation inside `android/app/build.gradle` on how to use it. ******************************************************************************** """ .trimIndent()) } } // We set an empty react[] map so if a library is reading it, they will find empty values. project.extensions.extraProperties.set("react", mapOf<String, String>()) }
-
配置Java工具链,确保项目中的 Java 和 Kotlin 代码使用 Java 17 版本
fun configureJavaToolChains(input: Project) { // Check at the app level if react.internal.disableJavaVersionAlignment is set. if (input.hasProperty(INTERNAL_DISABLE_JAVA_VERSION_ALIGNMENT)) { return } input.rootProject.allprojects { project -> // Allows every single module to set react.internal.disableJavaVersionAlignment also. if (project.hasProperty(INTERNAL_DISABLE_JAVA_VERSION_ALIGNMENT)) { return@allprojects } val action = Action<AppliedPlugin> { project.extensions.getByType(AndroidComponentsExtension::class.java) .finalizeDsl { ext -> ext.compileOptions.sourceCompatibility = JavaVersion.VERSION_17 ext.compileOptions.targetCompatibility = JavaVersion.VERSION_17 } } project.pluginManager.withPlugin("com.android.application", action) project.pluginManager.withPlugin("com.android.library", action) project.pluginManager.withPlugin("org.jetbrains.kotlin.android") { project.extensions.getByType(KotlinTopLevelExtension::class.java).jvmToolchain(17) } project.pluginManager.withPlugin("org.jetbrains.kotlin.jvm") { project.extensions.getByType(KotlinTopLevelExtension::class.java).jvmToolchain(17) } } }
-
根据不同的构建类型配置不同的任务
//根据不同的构建类型配置不同的任务 project.extensions.getByType(AndroidComponentsExtension::class.java).apply { onVariants(selector().all()) { variant -> //配置react任务,用于执行react-native的打包操作 project.configureReactTasks(variant = variant, config = extension) } }
configureReactTasks 扩展方法
internal fun Project.configureReactTasks(variant: Variant, config: ReactExtension) { val targetName = variant.name.capitalizeCompat() val targetPath = variant.name val buildDir = this.layout.buildDirectory.get().asFile // Resources: generated/assets/react/<variant>/index.android.bundle val resourcesDir = File(buildDir, "generated/res/react/$targetPath") // Bundle: generated/assets/react/<variant>/index.android.bundle val jsBundleDir = File(buildDir, "generated/assets/react/$targetPath") // Sourcemap: generated/sourcemaps/react/<variant>/index.android.bundle.map val jsSourceMapsDir = File(buildDir, "generated/sourcemaps/react/$targetPath") // Intermediate packager: // intermediates/sourcemaps/react/<variant>/index.android.bundle.packager.map // Intermediate compiler: // intermediates/sourcemaps/react/<variant>/index.android.bundle.compiler.map val jsIntermediateSourceMapsDir = File(buildDir, "intermediates/sourcemaps/react/$targetPath") // The location of the cli.js file for React Native val cliFile = detectedCliFile(config) val isHermesEnabledInProject = project.isHermesEnabled val isHermesEnabledInThisVariant = if (config.enableHermesOnlyInVariants.get().isNotEmpty()) { config.enableHermesOnlyInVariants.get() .contains(variant.name) && isHermesEnabledInProject } else { isHermesEnabledInProject } val isDebuggableVariant = config.debuggableVariants.get().any { it.equals(variant.name, ignoreCase = true) } //配置新架构打包选项 configureNewArchPackagingOptions(project, config, variant) //配置JS引擎打包选项 configureJsEnginePackagingOptions(config, variant, isHermesEnabledInThisVariant) if (!isDebuggableVariant) { val entryFileEnvVariable = System.getenv("ENTRY_FILE") val bundleTask = tasks.register("createBundle${targetName}JsAndAssets", BundleHermesCTask::class.java) { it.root.set(config.root) it.nodeExecutableAndArgs.set(config.nodeExecutableAndArgs) it.cliFile.set(cliFile) it.bundleCommand.set(config.bundleCommand) it.entryFile.set(detectedEntryFile(config, entryFileEnvVariable)) it.extraPackagerArgs.set(config.extraPackagerArgs) it.bundleConfig.set(config.bundleConfig) it.bundleAssetName.set(config.bundleAssetName) it.jsBundleDir.set(jsBundleDir) it.resourcesDir.set(resourcesDir) it.hermesEnabled.set(isHermesEnabledInThisVariant) it.minifyEnabled.set(!isHermesEnabledInThisVariant) it.devEnabled.set(false) it.jsIntermediateSourceMapsDir.set(jsIntermediateSourceMapsDir) it.jsSourceMapsDir.set(jsSourceMapsDir) it.hermesCommand.set(config.hermesCommand) it.hermesFlags.set(config.hermesFlags) it.reactNativeDir.set(config.reactNativeDir) } //将生成的资源目录添加到源集 variant.sources.res?.addGeneratedSourceDirectory( bundleTask, BundleHermesCTask::resourcesDir ) variant.sources.assets?.addGeneratedSourceDirectory( bundleTask, BundleHermesCTask::jsBundleDir ) } }
-
配置react-native-codegen,用于生成所需代码,帮助我们避免编写重复代码的工具。
-
配置library项目的构建配置字段
/** * 配置构建配置字段 * @param appProject Project */ fun configureBuildConfigFieldsForLibraries(appProject: Project) { appProject.rootProject.allprojects { subproject -> subproject.pluginManager.withPlugin("com.android.library") { subproject.extensions.getByType(AndroidComponentsExtension::class.java) .finalizeDsl { ext -> ext.buildFeatures.buildConfig = true } } } }
-
配置library项目的namespace
fun configureNamespaceForLibraries(appProject: Project) { appProject.rootProject.allprojects { subproject -> subproject.pluginManager.withPlugin("com.android.library") { subproject.extensions.getByType(AndroidComponentsExtension::class.java) .finalizeDsl { ext -> if (ext.namespace == null) { val android = subproject.extensions.getByType(LibraryExtension::class.java) val manifestFile = android.sourceSets.getByName("main").manifest.srcFile manifestFile .takeIf { it.exists() } ?.let { file -> getPackageNameFromManifest(file)?.let { packageName -> ext.namespace = packageName } } } } } } }
-
如果项目中使用了com.android.library插件,也就是library模块中会执行以下代码
- 配置react-native-codegen,用于生成所需代码,帮助我们避免编写重复代码的工具。
总结
到这里,我们基本就清楚了react-native-gradle-plugin
这个插件的主要作用
- 做了一些编译环境的检查
- 创建了一些配置项,用于在app项目和库项目之间共享配置
- 统一替换项目中的react-native相关的依赖,并确保版本一致
- 配置任务,包括打包,生成代码等
但是插件中并没有看到RN三方库依赖处理的逻辑,所以,并没有解答我们一开始的问题,我们接着分析。
篇幅原因,本篇文章就到这里,下面我们来分析一下 native_modules.gradle
的作用是什么。
感谢阅读,觉有有帮助点赞支持,如果有任何疑问或建议,欢迎在评论区留言。如需转载,请注明出处:喻志强的博客 ,谢谢!