前一段时间由于业务需要,接触了下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 = 21compileSdkVersion = 34targetSdkVersion = 34ndkVersion = "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.ndkVersioncompileSdk rootProject.ext.compileSdkVersionnamespace "com.yzq.rn_project_analysis"defaultConfig {applicationId "com.yzq.rn_project_analysis"minSdkVersion rootProject.ext.minSdkVersiontargetSdkVersion rootProject.ext.targetSdkVersionversionCode 1versionName "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.debugminifyEnabled enableProguardInReleaseBuildsproguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"}}
}dependencies {// The version of react-native is set by the React Native Gradle Pluginimplementation("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版本,不能低于17checkJvmVersion(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().asFileval propertiesFile = File(reactNativeDir, "ReactAndroid/gradle.properties")//获取版本号和groupNameval versionAndGroupStrings = readVersionAndGroupStrings(propertiesFile)val versionString = versionAndGroupStrings.firstval groupString = versionAndGroupStrings.second//配置依赖,主要是做了依赖替换和统一版本的逻辑configureDependencies(project, versionString, groupString)//配置仓库configureRepositories(project, reactNativeDir)}//配置NDKconfigureReactNativeNdk(project, extension)//配置App的构建配置字段configureBuildConfigFieldsForApp(project, extension)//配置开发端口 默认8081configureDevPorts(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 ConfigurationconfigureBuildConfigFieldsForLibraries(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?.majorVersionprintln("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 artifactsval groupString =reactAndroidProperties[INTERNAL_PUBLISHING_GROUP] as? String?: DEFAULT_INTERNAL_PUBLISHING_GROUPreturn 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-androiddependencySubstitution.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-androiddependencySubstitution.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 groupif (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 StringmavenRepoFromURI(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 npmmavenRepoFromURI(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 NDKreturn@finalizeDsl}// We enable prefab so users can consume .so/headers from ReactAndroid and hermes-engine// .aarext.buildFeatures.prefab = true// If the user has not provided a CmakeLists.txt path, let's provide// the default one from the frameworkif (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.argumentsif (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/26if (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 = trueext.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_PORTval inspectorProxyPort =project.properties["reactNativeInspectorProxyPort"]?.toString() ?: devServerPortval 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_17ext.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.nameval buildDir = this.layout.buildDirectory.get().asFile// Resources: generated/assets/react/<variant>/index.android.bundleval resourcesDir = File(buildDir, "generated/res/react/$targetPath")// Bundle: generated/assets/react/<variant>/index.android.bundleval jsBundleDir = File(buildDir, "generated/assets/react/$targetPath")// Sourcemap: generated/sourcemaps/react/<variant>/index.android.bundle.mapval 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.mapval jsIntermediateSourceMapsDir = File(buildDir, "intermediates/sourcemaps/react/$targetPath")// The location of the cli.js file for React Nativeval cliFile = detectedCliFile(config)val isHermesEnabledInProject = project.isHermesEnabledval 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.srcFilemanifestFile.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
的作用是什么。
感谢阅读,觉有有帮助点赞支持,如果有任何疑问或建议,欢迎在评论区留言。如需转载,请注明出处:喻志强的博客 ,谢谢!