ReactNative项目构建分析与思考之react-native-gradle-plugin

前一段时间由于业务需要,接触了下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)}}

插件主要有以下逻辑

  1. 检查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)}}
  1. 创建react配置
    val extension = project.extensions.create("react", ReactExtension::class.java, project)
  1. 在根项目创建一个私有的配置项 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"))
}
  1. 如果项目中使用了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,用于生成所需代码,帮助我们避免编写重复代码的工具。

  1. 配置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}}}}
  2. 配置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}}}}}}}
    
  3. 如果项目中使用了com.android.library插件,也就是library模块中会执行以下代码

    • 配置react-native-codegen,用于生成所需代码,帮助我们避免编写重复代码的工具。

总结

到这里,我们基本就清楚了react-native-gradle-plugin这个插件的主要作用

  • 做了一些编译环境的检查
  • 创建了一些配置项,用于在app项目和库项目之间共享配置
  • 统一替换项目中的react-native相关的依赖,并确保版本一致
  • 配置任务,包括打包,生成代码等

但是插件中并没有看到RN三方库依赖处理的逻辑,所以,并没有解答我们一开始的问题,我们接着分析。

篇幅原因,本篇文章就到这里,下面我们来分析一下 native_modules.gradle 的作用是什么。


感谢阅读,觉有有帮助点赞支持,如果有任何疑问或建议,欢迎在评论区留言。如需转载,请注明出处:喻志强的博客 ,谢谢!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/272301.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

阿珊详解Vue Router的守卫机制

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

用一个 Python 脚本实现依次运行其他多个带 argparse 命令行参数的 .py 文件

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 问题描述&#xff1a;在 Windows 环境中&#xff0c;您希望通过一个 Python 脚本来实现特定的自动化任务&#xff0c;该任务需要依次运行其他多个带 argparse 命令行参数的 .py 文件。您希望找到一种简…

CXYGZL实现钉钉、飞书和微信全面覆盖!!!

非常欣慰能在这里与大家分享&#xff0c;CXYGZL已圆满实现多端互通的目标&#xff01;&#xff01;&#xff01; 无论您是在手机、电脑还是平板上使用钉钉、企微还是飞书&#xff0c;只需将CXYGZL轻松集成到您的办公软件中&#xff0c;即可实现无缝审批处理各项任务&#xff0c…

计算机找不到msvcr120.dll的五种修复方法,轻松搞定msvcr120.dll丢失问题

当计算机系统中msvcr120.dll文件丢失时&#xff0c;可能会引发一系列运行问题和故障现象。msvcr120.dll是Microsoft Visual C Redistributable Package的一部分&#xff0c;对于许多Windows应用程序的正常运行至关重要。由于msvcr120.dll是许多软件在运行过程中依赖的重要动态链…

Java项目:44 ssm003在线医疗服务系统+jsp(含文档)

作者主页&#xff1a;源码空间codegym 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 主要功能 前台登录&#xff1a; 注册用户&#xff1a;用户名、密码、姓名、联系电话 注册医生&#xff1a;医生工号、密码、医生姓名、职称、…

idea:springboot项目搭建

目录 一、创建项目 1、File → New → Project 2、Spring Initializr → Next 3、填写信息 → Next 4、web → Spring Web → Next 5、填写信息 → Finish 6、处理配置不合理内容 7、注意事项 7.1 有依赖包&#xff0c;却显示找不到依赖&#xff0c;刷新一下maven 7.…

基于 HBase Phoenix 构建实时数仓(2)—— HBase 完全分布式安装

目录 一、开启 HDFS 机柜感知 1. 增加 core-site.xml 配置项 2. 创建机柜感知脚本 3. 创建机柜配置信息文件 4. 分发相关文件到其它节点 5. 重启 HDFS 使机柜感知生效 二、主机规划 三、安装配置 HBase 完全分布式集群 1. 在所有节点上配置环境变量 2. 解压、配置环境…

瑞_Redis_短信登录(一)

文章目录 项目介绍1 短信登录1.1 项目准备1.1.1 导入SQL1.1.2 导入后端项目1.1.3 导入前端项目 &#x1f64a; 前言&#xff1a;本文章为瑞_系列专栏之《Redis》的实战篇的短信登录章节的项目准备小节。由于博主是从B站黑马程序员的《Redis》学习其相关知识&#xff0c;所以本系…

2023年12月CCF-GESP编程能力等级认证Python编程七级真题解析

本文收录于专栏《Python等级认证CCF-GESP真题解析》,专栏总目录・点这里 一、单选题(每题 2 分,共 30 分) 第1题 假设变量 x 为 float 类型,如果下面代码输入为 100,输出最接近( )。 A.0 B.-5 C.-8 D.8 答案:B 第2题 对于下面动态规划方法实现的函数,以下选项中…

简单BFF架构设计

又到周五了有了一个小时的闲暇时间简单写点东西&#xff0c;介绍一个简单的BFF的架构。BFF:Backends For Frontends,其实现在是个比较常见的前端架构设计的方案&#xff0c;其最大的优势便在于前端可以高度自由的在Node层做一些server端才可以做的东西&#xff0c;比如SSR、登录…

Vue保姆级项目教程:十万字零基础开发信贷管理系统!

项目简介与搭建过程 项目简介 需求背景 信贷管理系统是一种用于银行、金融机构或其他借贷组织用于管理信贷流程的软件系统。它可以帮助机构管理贷款申请、贷款审批、合同管理等相关流程,提高信贷业务的效率和准确性。 需求描述 本需求文档旨在定义信贷管理系统的功能和特…

代码随想录训练营第四天|面试题02.07链表相交

题目&#xff1a; 面试题 02.07. 链表相交 已解答 简单 相关标签 相关企业 提示 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 题目…

设计模式(工厂模式)

设计模式&#xff08;工厂模式&#xff09; 一、工厂模式介绍 在工厂模式中&#xff0c;父类决定生成示例的方式&#xff0c;但不决定所要生成的具体的类&#xff0c;具体的处理部分交给子类负责。这样就可以将生成示例的框架和生成示例的类解耦。 二、示例程序 以下示例程…

ARM中汇编语言的学习(加法、乘法、除法、左移、右移、按位与等多种命令操作实例以及ARM的 N、Z、C、V 标志位的解释)

汇编概述 汇编需要学习的大致框架如下&#xff1a; 汇编中的符号 1.指令&#xff1b;能够北嘁肷梢惶?2bit机器码&#xff0c;并且能够被cpui识别和执行 2.伪指令&#xff1a;本身不是指令&#xff0c;编译器可以将其替换成若干条指令 3.伪操作&#xff1a;不会生成指令…

Kafka | SpringBoot集成Kafka

SpringBoot集成Kafka 一、前言二、项目1. pom2. application.properties4. 消息生产者-测试5. 消息消费者 三、启动测试四、有总结的不对的地方/或者问题 请指正, 我在努力中 一、前言 该文章中主要对SpringBoot 集成Kafka 主要是 application.properties 与 pom坐标就算集成完…

HTML5基础2

drag 可以把拖放事件拆分成4个步骤 设置元素为可拖放。为了使元素可拖动&#xff0c;把 draggable 属性设置为 true 。 <img draggable"true"> 拖动什么。ondragstart 和 setData() const dragestart (ev)>{ev.dataTransfer.setData(play,ev.target.id)} …

[云原生] k8s之存储卷

一、emptyDir存储卷 当Pod被分配给节点时&#xff0c;首先创建emptyDir卷&#xff0c;并且只要该Pod在该节点上运行&#xff0c;该卷就会存在。正如卷的名字所述&#xff0c;它最初是空的。Pod 中的容器可以读取和写入emptyDir卷中的相同文件&#xff0c;尽管该卷可以挂载到每…

如何不丢精度保存PPT中的图片,实测有效

1.在powerpoint软件中 文件-》选项 -》高级-》设置为不压缩&#xff0c;且默认输出为最高 2.导入对应图片后&#xff0c;右键导出图片&#xff0c;选择.emf文件 3.使用windows自带的画图工具打开.emf文件&#xff0c;ctrls另存为.png文件 此方法亲测可以生成清晰度很高的图片

python:布伊山德U检验(Buishand U test,BUT)突变点检测(以NDVI时间序列为例)

作者:CSDN @ _养乐多_ 本文将介绍布伊山德U检验(Buishand U test,BUT)突变点检测代码。以 NDVI 时间序列为例。输入数据可以是csv,一列NDVI值,一列时间。代码可以扩展到遥感时间序列突变检测(突变年份、突变幅度等)中。 结果如下图所示, 文章目录 一、准备数据二、…

【JavaEE进阶】 @Transactional详解

文章目录 &#x1f343;前言&#x1f332;rollbackFor&#xff08;异常回滚属性&#xff09;&#x1f384;事务隔离级别&#x1f6a9;MySQL事务隔离级别&#x1f6a9;Spring事务隔离级别 &#x1f38b;Spring事务传播机制&#x1f6a9;什么是事务传播机制&#x1f6a9;事务有哪…