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.mzph.cn/news/733098.shtml

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

相关文章

西门子PLC中的程序块及类别详解

在PLC的编程中&#xff0c;程序块是指一组逻辑控制代码&#xff0c;用于实现系统中特定的控制功能。程序块主要分为四类&#xff0c;包括函数块&#xff08;FB&#xff09;、函数&#xff08;FC&#xff09;、数据块&#xff08;DB&#xff09;和组织块&#xff08;OB&#xff…

300分钟吃透分布式缓存-24讲:Redis崩溃后,如何进行数据恢复的?

Redis 持久化是一个将内存数据转储到磁盘的过程。Redis 目前支持 RDB、AOF&#xff0c;以及混合存储三种模式。 RDB Redis 的 RDB 持久化是以快照的方式将内存数据存储到磁盘。在需要进行 RDB 持久化时&#xff0c;Redis 会将内存中的所有数据以二进制的格式落地&#xff0c;每…

Python算法题集_在排序数组中查找元素的第一个和最后一个位置

Python算法题集_在排序数组中查找元素的第一个和最后一个位置 题34&#xff1a;在排序数组中查找元素的第一个和最后一个位置1. 示例说明2. 题目解析- 题意分解- 优化思路- 测量工具 3. 代码展开1) 标准求解【二分法两次左边界】2) 改进版一【二分法左右边界】3) 改进版二【第三…

基于YOLOv8深度学习的葡萄病害智能诊断与防治系统【python源码+Pyqt5界面+数据集+训练代码】深度学习实战

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

寻找完全平方数——浮点数陷阱

【题目描述】 输出所有形如aabb的4位完全平方数&#xff08;即前两位数字相等&#xff0c;后两位数字也相等&#xff09;。 【解析】 一、问题分析 从问题出发&#xff0c;题目要求输出的是满足一定条件的数。数在计算机中是要占存储空间的&#xff0c;要在计算机中表示一个…

C语言字符串型常量

在C语言中&#xff0c;字符串型常量是由一系列字符组成的常量。字符串常量在C中以双引号&#xff08;"&#xff09;括起来&#xff0c;例如&#xff1a;“Hello, World!”。字符串常量在C中是不可变的&#xff0c;也就是说&#xff0c;一旦定义&#xff0c;就不能修改其内…

Web自动化测试流程:从入门到精通,帮你成为测试专家

摘要&#xff1a; Web应用程序在今天的软件开发中占据着越来越重要的地位。保证Web应用程序的质量和稳定性是非常必要的&#xff0c;而自动化测试是一种有效的方法。本文将介绍Web自动化测试流程&#xff0c;并提供代码示例。 步骤一&#xff1a;选取测试工具 选择适合自己团…

像SpringBoot一样使用Flask - 5.统一处理(日志、异常、响应报文)

接上文《像SpringBoot一样使用Flask - 4.拦截器》&#xff0c;通过拦截器处理一些日志&#xff0c;异常、还有统一的响应报文。 统一的目的就是为了让前后端调用请求不会因为各自习惯而随意编写&#xff0c;增加技术人员快速上手及代码的可阅读性。 一、定义一个返回类。是不是…

【前端Vue】Vue从0基础到大神完整教程第1篇:vue基本概念,vue-cli的使用【附代码文档】

Vue从0基础到大神学习完整教程完整教程&#xff08;附代码资料&#xff09;主要内容讲述&#xff1a;vue基本概念&#xff0c;vue-cli的使用&#xff0c;vue的插值表达式&#xff0c;{{ gaga }}&#xff0c;{{ if (obj.age > 18 ) { } }}&#xff0c;vue指令&#xff0c;综合…

20行代码搞定PDF表格转为Excel表

1.环境准备 安装好python并且配置好环境安装pdfplumber、xlwt库使用Vscode或者PyCharm等编辑器 在pycharm中如果报红&#xff0c;可以鼠标点击报红的库&#xff0c;altenter进行安装 2.代码部分 import pdfplumber import xlwt # 读取源pdf文件 pdf pdfplumber.open("…

图像处理ASIC设计方法 笔记8 卷积计算芯片的结构

(一) P81 卷积芯片内部模板框图 该设计有两个数据通路:图像数据和模板数据。 图像数据是经过帧控制、实时图SPRAM(写控制、 SPRAM读控制、数据读控制)、计算单元; 模板数据是经过模板SPRAM、计算单元。 4.5.4运算单元像素寄存器控制 存储SPRAM写入的64bit数据,输出为…

Unity2023.1.19_DOTS_JobSystem

Unity2023.1.19_DOTS_JobSystem 上篇我们知道了DOTS是包含Entity Component System&#xff0c;Job System&#xff0c;Burst compiler三者的。接下来看下JobSystem的工作原理和具体实现。 简介&#xff1a; 官方介绍说&#xff1a;JobSystem允许您编写简单而安全的多线程代…

C++篇 语 句

到目前为止&#xff0c;我们只见过两种语句&#xff1a; return 语句和表达式语句。根据语句对执行顺 序的影响&#xff0c;C 语言其余语句大多属于以下 3 大类。 选择语句&#xff1a; if 语句和 switch 语句。循环语句&#xff1a; while 语句&#xff0c; do...while 语句和…

XSS漏洞-存储型漏洞案例

一、环境 在github上找DVWA的靶机环境 DVWA存储库 二、复现 先将其安全等级改为低 然后点击存储型的xss&#xff0c;先随便写几条看看现象 可以看到我们写的都展示在了下面的框中 看看源码&#xff0c;分析在存储时的漏洞 我们可以看到&#xff0c;在对数据插入的时候&am…

筛选出等于1的式子

然后统计和归类 归类分行归类方法 算术符号归类 数字大小排序算术符号归类 import randomdef generate_expression(num_range, num_count, operators):nums random.sample(range(num_range[0], num_range[1]1), num_count)ops random.choices(operators, knum_count-1)expre…

ChatGPT 结合实际地图实现问答式地图检索功能基于Function calling

ChatGPT 结合实际地图实现问答式地图检索功能基于Function calling ChatGPT结合实际业务&#xff0c;主要是研发多函数调用&#xff08;Function Calling&#xff09;功能模块&#xff0c;将自定义函数通过ChatGPT 问答结果&#xff0c;实现对应函数执行&#xff0c;再次将结果…

List(CS61B学习记录)

问题引入 上图中&#xff0c;赋给b海象的weight会改变a海象的weight&#xff0c;但x的赋值又不会改变y的赋值 Bits 要解释上图的问题&#xff0c;我们应该从Java的底层入手 相同的二进制编码&#xff0c;却因为数据类型不同&#xff0c;输出不同的值 变量的声明 基本类型…

一步到位!快速精通Git工作流及实战技巧详解

Git是一个分布式版本控制系统。 1、git的应用场景 1.备份 小明负责的模块就要完成了&#xff0c;就在即将release之前的一瞬间&#xff0c;电脑突然蓝屏。硬盘光荣牺牲&#xff01;几个月来的努力付之东流。 场景二&#xff1a;代码还原 这个项目中需要一个很复杂的功能&…

CVHub | 初识langchain,3分钟快速了解!

本文来源公众号“CVHub”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;初识langchain 1 什么是langchain langchain[1]是一个用于构建LLM-Based应用的框架&#xff0c;提供以下能力: 上下文感知&#xff1a;可以为LLM链接上下文…

《Python源码剖析》之字符串拼接的一个效率问题

前言 我们常用的字符串拼接方法有两个&#xff0c;一个是通过“”号实现字符串的拼接&#xff0c;还一个就是通过join方法来实现拼接&#xff0c;前者在写法上更加便利&#xff0c;和数字之间的加法运算一样&#xff0c;通常只有两个运算对象&#xff0c;只不过他们的运算规则…