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 = 21
        compileSdkVersion = 34
        targetSdkVersion = 34
        ndkVersion = "25.1.8937393"
        kotlinVersion = "1.8.0"
    }
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath("com.android.tools.build:gradle")
        //RN插件
        classpath("com.facebook.react:react-native-gradle-plugin")
        classpath("org.jetbrains.kotlin:kotlin-gradle-plugin")
    }
}
//应用了一个叫做com.facebook.react.rootproject的插件
apply plugin: "com.facebook.react.rootproject"

接着看下app目录下的build.gradle文件

apply plugin: "com.android.application"
apply plugin: "org.jetbrains.kotlin.android"
//应用了一个叫做com.facebook.react的插件
apply plugin: "com.facebook.react"

/**
 * This is the configuration block to customize your React Native Android app.
 * By default you don't need to apply any configuration, just uncomment the lines you need.
 */
react {
    /* Folders */
    //   The root of your project, i.e. where "package.json" lives. Default is '..'
    // root = file("../")
    //   The folder where the react-native NPM package is. Default is ../node_modules/react-native
    // reactNativeDir = file("../node_modules/react-native")
    //   The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen
    // codegenDir = file("../node_modules/@react-native/codegen")
    //   The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js
    // cliFile = file("../node_modules/react-native/cli.js")

    /* Variants */
    //   The list of variants to that are debuggable. For those we're going to
    //   skip the bundling of the JS bundle and the assets. By default is just 'debug'.
    //   If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.
    // debuggableVariants = ["liteDebug", "prodDebug"]

    /* Bundling */
    //   A list containing the node command and its flags. Default is just 'node'.
    // nodeExecutableAndArgs = ["node"]
    //
    //   The command to run when bundling. By default is 'bundle'
    // bundleCommand = "ram-bundle"
    //
    //   The path to the CLI configuration file. Default is empty.
    // bundleConfig = file(../rn-cli.config.js)
    //
    //   The name of the generated asset file containing your JS bundle
    // bundleAssetName = "MyApplication.android.bundle"
    //
    //   The entry file for bundle generation. Default is 'index.android.js' or 'index.js'
    // entryFile = file("../js/MyApplication.android.js")
    //
    //   A list of extra flags to pass to the 'bundle' commands.
    //   See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle
    // extraPackagerArgs = []

    /* Hermes Commands */
    //   The hermes compiler command to run. By default it is 'hermesc'
    // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc"
    //
    //   The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
    // hermesFlags = ["-O", "-output-source-map"]
}

/**
 * Set this to true to Run Proguard on Release builds to minify the Java bytecode.
 */
def enableProguardInReleaseBuilds = false

/**
 * The preferred build flavor of JavaScriptCore (JSC)
 *
 * For example, to use the international variant, you can use:
 * `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
 *
 * The international variant includes ICU i18n library and necessary data
 * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
 * give correct results when using with locales other than en-US. Note that
 * this variant is about 6MiB larger per architecture than default.
 */
def jscFlavor = 'org.webkit:android-jsc:+'

android {
    ndkVersion rootProject.ext.ndkVersion
    compileSdk rootProject.ext.compileSdkVersion

    namespace "com.yzq.rn_project_analysis"
    defaultConfig {
        applicationId "com.yzq.rn_project_analysis"
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.targetSdkVersion
        versionCode 1
        versionName "1.0"
    }
    signingConfigs {
        debug {
            storeFile file('debug.keystore')
            storePassword 'android'
            keyAlias 'androiddebugkey'
            keyPassword 'android'
        }
    }
    buildTypes {
        debug {
            signingConfig signingConfigs.debug
        }
        release {
            // Caution! In production, you need to generate your own keystore file.
            // see https://reactnative.dev/docs/signed-apk-android.
            signingConfig signingConfigs.debug
            minifyEnabled enableProguardInReleaseBuilds
            proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
        }
    }
}

dependencies {
    // The version of react-native is set by the React Native Gradle Plugin
    implementation("com.facebook.react:react-android")
    implementation("com.facebook.react:flipper-integration")

    if (hermesEnabled.toBoolean()) {
        implementation("com.facebook.react:hermes-android")
    } else {
        implementation jscFlavor
    }
}
//应用了一个脚本文件
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)

可以看到,工程的依赖配置也比较的清晰,主要是配置了一些Android的基本配置,然后应用了RN的插件和脚本。


三方库使用

三方库在RN中有着非常重要的地位,因为RN本身的功能是有限的,所以需要依赖一些三方库来实现一些功能。

三方库一般提供了跨平台的支持,对前端开发同学来讲是非常友好的,不需要去了解原生的开发技术,就可以实现一些原生的功能。

三方库的使用方式非常简单,按照使用三方库文档来就可以了。
下面随便去 https://reactnative.directory/ 找一个三方库来简单使用一下看看。

就以 react-native-device-info 为例吧

在项目根目录下执行下面命令安装即可

yarn add react-native-device-info

安装完成后会发现,项目根目录下的package.json文件中多了一条依赖

在这里插入图片描述

然后在RN项目中使用一下

import DeviceInfo from "react-native-device-info";

<Button title={"deviceInfo"} onPress={() => {
    DeviceInfo.getAndroidId().then((id) => {
        console.log(id);
    })

}}/>

然后重新运行项目, 点击按钮,就可以看到控制台输出了设备的id
在这里插入图片描述
使用起来非常简单,可以看到,这里实际上完全不需要关心native端的代码,就可以实现一些原生的功能。

那作为 native 端开发的同学,这里不免就会好奇一个问题:
正常来讲如果我们在原生项目中使用三方库,是需要引入三方库的jar包或者aar包的,大部分sdk还需要进行初始化操作,然后才能调用相关的方法,

只需要一个yarn add react-native-device-info就能让RN项目使用原生的功能,这是怎么做到的呢?

带着这个问题,先来看看Android项目有什么变化。
在这里插入图片描述

这个module是怎么引入的呢?正常来讲在Android中我们想要引入一个本地的module,需要在settings.gradle中include进来,然后在build.gradle中引入依赖。

但是,再次去看看settings.gradle和build.gradle文件,发现并没有类似的代码,那这个module是怎么引入的呢?

还记得上面在分析项目结构的时候,我们提到的一个脚本和一个插件吗?

  • apply from: file(“…/node_modules/@react-native-community/cli-platform-android/native_modules.gradle”);
  • includeBuild(‘…/node_modules/@react-native/gradle-plugin’)

实际上,这两个东西就是管理RN Android项目的配置和依赖的,是非常重要的角色。

react-native-gradle-plugin 分析

我们先来分析一下react-native-gradle-plugin这个插件,这个插件是RN项目的核心插件,它的作用是管理RN项目的依赖和配置。

在这里插入图片描述

通过源码配置可以看到,一共提供了两个插件

  • com.facebook.react
  • com.facebook.react.rootproject
com.facebook.react.rootproject

我们先来看看 com.facebook.react.rootproject
该插件在项目的根目录下的build.gradle文件中被应用了



/**
 * 该插件应用于`android/build.gradle`文件。
 * 该插件的作用是确保app项目在库项目之前被配置,以便在库项目被配置时可以使用app项目的配置
 *
 * @constructor
 */
class ReactRootProjectPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        project.subprojects {
            // As the :app project (i.e. ReactPlugin) configures both namespaces and JVM toolchains
            // for libraries, its evaluation must happen before the libraries' evaluation.
            // Eventually the configuration of namespace/JVM toolchain can be moved inside this plugin.
            if (it.path != ":app") {
                it.evaluationDependsOn(":app")
            }
        }
    }
}

代码非常少,其作用就是是确保app项目在库项目之前被配置,以便在库项目被配置时可以使用app项目的配置。
简单说就是app中会有一些rn相关的配置,一些三方库中也会用到这些配置,此时需要确保app项目的配置在库项目之前被配置,以确保其他模块能够正常使用。

com.facebook.react

该插件是在app项目的build.gradle文件中被应用了

这个插件的代码相对多一些,我们来一点一点的分析

    override fun apply(project: Project) {
        //检查JVM版本,不能低于17
        checkJvmVersion(project)
        //创建react配置
        val extension = project.extensions.create("react", ReactExtension::class.java, project)

        // We register a private extension on the rootProject so that project wide configs
        // like codegen config can be propagated from app project to libraries.
        /**
         * 在根项目创建一个私有的配置项 privateReact,如果已经存在则获取
         * 用于在app项目和库项目之间共享配置
         */
        val rootExtension =
            project.rootProject.extensions.findByType(PrivateReactExtension::class.java)
                ?: project.rootProject.extensions.create(
                    "privateReact", PrivateReactExtension::class.java, project
                )

        // App Only Configuration
        /**
         * 如果项目中使用了com.android.application插件,也就是app模块中会执行以下代码
         */
        project.pluginManager.withPlugin("com.android.application") {
            // We wire the root extension with the values coming from the app (either user populated or
            // defaults).

            /**
             * 下面代码实际上就是把用户自定义的配置赋值给rootExtension,就是把用户自定义的配置传递给上面创建好的一个私有配置项 privateReact
             */
            rootExtension.root.set(extension.root)
            rootExtension.reactNativeDir.set(extension.reactNativeDir)
            rootExtension.codegenDir.set(extension.codegenDir)
            rootExtension.nodeExecutableAndArgs.set(extension.nodeExecutableAndArgs)

            println("rootExtension root: ${rootExtension.root.get()}")
            println("rootExtension reactNativeDir: ${rootExtension.reactNativeDir.get()}")
            println("rootExtension codegenDir: ${rootExtension.codegenDir.get()}")
            println("rootExtension nodeExecutableAndArgs: ${rootExtension.nodeExecutableAndArgs.get()}")


            /**
             * 项目配置完成后,执行以下代码
             */
            project.afterEvaluate {
                val reactNativeDir = extension.reactNativeDir.get().asFile
                val propertiesFile = File(reactNativeDir, "ReactAndroid/gradle.properties")

                //获取版本号和groupName
                val versionAndGroupStrings = readVersionAndGroupStrings(propertiesFile)
                val versionString = versionAndGroupStrings.first
                val groupString = versionAndGroupStrings.second
                //配置依赖,主要是做了依赖替换和统一版本的逻辑
                configureDependencies(project, versionString, groupString)
                //配置仓库
                configureRepositories(project, reactNativeDir)
            }

            //配置NDK
            configureReactNativeNdk(project, extension)
            //配置App的构建配置字段
            configureBuildConfigFieldsForApp(project, extension)
            //配置开发端口 默认8081
            configureDevPorts(project)
            //处理老版本配置兼容性
            configureBackwardCompatibilityReactMap(project)
            //配置Java工具链,确保项目中的 Java 和 Kotlin 代码使用 Java 17 版本
            configureJavaToolChains(project)

            //根据不同的构建类型配置不同的任务
            project.extensions.getByType(AndroidComponentsExtension::class.java).apply {
                onVariants(selector().all()) { variant ->
                    //配置react任务,用于执行react-native的打包操作
                    project.configureReactTasks(variant = variant, config = extension)
                }
            }
            //配置react-native-codegen,用于生成所需代码
            configureCodegen(project, extension, rootExtension, isLibrary = false)
        }

        // Library Only Configuration
        configureBuildConfigFieldsForLibraries(project)
        configureNamespaceForLibraries(project)
        project.pluginManager.withPlugin("com.android.library") {
            configureCodegen(project, extension, rootExtension, isLibrary = true)
        }
    }

插件主要有以下逻辑

  1. 检查JVM版本,不能低于17
    private fun checkJvmVersion(project: Project) {
        val jvmVersion = Jvm.current()?.javaVersion?.majorVersion
        println("jvmVersion: $jvmVersion")
        if ((jvmVersion?.toIntOrNull() ?: 0) <= 16) {
            project.logger.error(
                """

      ********************************************************************************

      ERROR: requires JDK17 or higher.
      Incompatible major version detected: '$jvmVersion'

      ********************************************************************************

      """
                    .trimIndent()
            )
            exitProcess(1)
        }
    }
  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 artifacts
            val groupString =
                reactAndroidProperties[INTERNAL_PUBLISHING_GROUP] as? String
                    ?: DEFAULT_INTERNAL_PUBLISHING_GROUP
            return Pair(versionString, groupString)
        }
    

    configureDependencies方法,主要做了依赖替换和统一版本的逻辑

    /**
    	     * 配置依赖
    	     * 1.替换依赖
    	     * 2.强制使用指定版本
    	     *
    	     * @param project Project
    	     * @param versionString String
    	     * @param groupString String
    	     */
    	    fun configureDependencies(
    	        project: Project,
    	        versionString: String,
    	        groupString: String = DEFAULT_INTERNAL_PUBLISHING_GROUP
    	    ) {
    	        println("configureDependencies: $versionString, $groupString")
    	        if (versionString.isBlank()) return
    	        //遍历所有项目
    	        project.rootProject.allprojects { eachProject ->
    	            println("eachProject: ${eachProject.name}")
    	            //遍历项目的所有配置
    	            eachProject.configurations.all { configuration ->
    	                /**
    	                 * configuration.resolutionStrategy 用于配置解析策略,一般用于配置依赖替换和强制使用指定版本
    	                 */
    	                configuration.resolutionStrategy.dependencySubstitution {
    	                    //获取依赖替换列表
    	                    getDependencySubstitutions(
    	                        versionString,
    	                        groupString
    	                    ).forEach { (module, dest, reason) ->
    	                        //将指定的依赖替换为目标依赖
    	                        it.substitute(it.module(module)).using(it.module(dest)).because(reason)
    	                    }
    	                }
    	                //强制使用指定版本
    	                configuration.resolutionStrategy.force(
    	                    "${groupString}:react-android:${versionString}",
    	                    "${groupString}:flipper-integration:${versionString}",
    	                )
    	
    	                //如果用户没有选择使用夜间版本进行本地开发,则强制使用hermes-android指定版本
    	                if (!(eachProject.findProperty(INTERNAL_USE_HERMES_NIGHTLY) as? String).toBoolean()) {
    	                    // Contributors only: The hermes-engine version is forced only if the user has
    	                    // not opted into using nightlies for local development.
    	                    configuration.resolutionStrategy.force("${groupString}:hermes-android:${versionString}")
    	                }
    	            }
    	        }
    	    }
    

    getDependencySubstitutions方法,主要是生成需要进行依赖替换的列表

        /**
         * 生成依赖替换列表
         * @param versionString String
         * @param groupString String
         * @return List<Triple<String, String, String>>
         */
        internal fun getDependencySubstitutions(
            versionString: String,
            groupString: String = DEFAULT_INTERNAL_PUBLISHING_GROUP
        ): List<Triple<String, String, String>> {
            /**
             * 生成依赖替换列表
             * first:原始依赖
             * second:替换后的依赖
             * third:原因
             */
            val dependencySubstitution = mutableListOf<Triple<String, String, String>>()
            // react-native替换为react-android
            dependencySubstitution.add(
                Triple(
                    "com.facebook.react:react-native",
                    "${groupString}:react-android:${versionString}",
                    "The react-native artifact was deprecated in favor of react-android due to https://github.com/facebook/react-native/issues/35210."
                )
            )
            // hermes-engine替换为hermes-android
            dependencySubstitution.add(
                Triple(
                    "com.facebook.react:hermes-engine",
                    "${groupString}:hermes-android:${versionString}",
                    "The hermes-engine artifact was deprecated in favor of hermes-android due to https://github.com/facebook/react-native/issues/35210."
                )
            )
            // 如果 groupString 不是默认值 com.facebook.react,则修改react-android和hermes-android的Maven group
            if (groupString != DEFAULT_INTERNAL_PUBLISHING_GROUP) {
                dependencySubstitution.add(
                    Triple(
                        "com.facebook.react:react-android",
                        "${groupString}:react-android:${versionString}",
                        "The react-android dependency was modified to use the correct Maven group."
                    )
                )
                dependencySubstitution.add(
                    Triple(
                        "com.facebook.react:hermes-android",
                        "${groupString}:hermes-android:${versionString}",
                        "The hermes-android dependency was modified to use the correct Maven group."
                    )
                )
            }
    
            return dependencySubstitution
        }
    
  • 配置仓库源,这个比较简单,就是配置了一些依赖所需的仓库地址

     fun configureRepositories(project: Project, reactNativeDir: File) {
            println("configureRepositories: $reactNativeDir")
            project.rootProject.allprojects { eachProject ->
                with(eachProject) {
                    if (hasProperty(INTERNAL_REACT_NATIVE_MAVEN_LOCAL_REPO)) {
                        val mavenLocalRepoPath =
                            property(INTERNAL_REACT_NATIVE_MAVEN_LOCAL_REPO) as String
                        mavenRepoFromURI(File(mavenLocalRepoPath).toURI())
                    }
                    // We add the snapshot for users on nightlies.
                    mavenRepoFromUrl("https://oss.sonatype.org/content/repositories/snapshots/")
                    repositories.mavenCentral { repo ->
                        // We don't want to fetch JSC from Maven Central as there are older versions there.
                        repo.content { it.excludeModule("org.webkit", "android-jsc") }
                    }
                    // Android JSC is installed from npm
                    mavenRepoFromURI(File(reactNativeDir, "../jsc-android/dist").toURI())
                    repositories.google()
                    mavenRepoFromUrl("https://www.jitpack.io")
                }
            }
        }
    
  • 配置NDK(Native Development Kit)相关设置

     fun configureReactNativeNdk(project: Project, extension: ReactExtension) {
            project.pluginManager.withPlugin("com.android.application") {
                project.extensions.getByType(AndroidComponentsExtension::class.java)
                    .finalizeDsl { ext ->
                        //是否启用新架构 没有直接返回
                        if (!project.isNewArchEnabled(extension)) {
                            // For Old Arch, we don't need to setup the NDK
                            return@finalizeDsl
                        }
                        // We enable prefab so users can consume .so/headers from ReactAndroid and hermes-engine
                        // .aar
                        ext.buildFeatures.prefab = true
    
                        // If the user has not provided a CmakeLists.txt path, let's provide
                        // the default one from the framework
                        if (ext.externalNativeBuild.cmake.path == null) {
                            ext.externalNativeBuild.cmake.path = File(
                                extension.reactNativeDir.get().asFile,
                                "ReactAndroid/cmake-utils/default-app-setup/CMakeLists.txt"
                            )
                        }
    
                        // Parameters should be provided in an additive manner (do not override what
                        // the user provided, but allow for sensible defaults).
                        val cmakeArgs = ext.defaultConfig.externalNativeBuild.cmake.arguments
                        if (cmakeArgs.none { it.startsWith("-DPROJECT_BUILD_DIR") }) {
                            cmakeArgs.add("-DPROJECT_BUILD_DIR=${project.layout.buildDirectory.get().asFile}")
                        }
                        if (cmakeArgs.none { it.startsWith("-DREACT_ANDROID_DIR") }) {
                            cmakeArgs.add(
                                "-DREACT_ANDROID_DIR=${
                                    extension.reactNativeDir.file("ReactAndroid").get().asFile
                                }"
                            )
                        }
                        if (cmakeArgs.none { it.startsWith("-DANDROID_STL") }) {
                            cmakeArgs.add("-DANDROID_STL=c++_shared")
                        }
                        // Due to the new NDK toolchain file, the C++ flags gets overridden between compilation
                        // units. This is causing some libraries to don't be compiled with -DANDROID and other
                        // crucial flags. This can be revisited once we bump to NDK 25/26
                        if (cmakeArgs.none { it.startsWith("-DANDROID_USE_LEGACY_TOOLCHAIN_FILE") }) {
                            cmakeArgs.add("-DANDROID_USE_LEGACY_TOOLCHAIN_FILE=ON")
                        }
    
                        val architectures = project.getReactNativeArchitectures()
                        // abiFilters are split ABI are not compatible each other, so we set the abiFilters
                        // only if the user hasn't enabled the split abi feature.
                        if (architectures.isNotEmpty() && !ext.splits.abi.isEnable) {
                            ext.defaultConfig.ndk.abiFilters.addAll(architectures)
                        }
                    }
            }
        }
    
  • 配置App的构建配置字段

    		    /**
    		     * 确保在 Android 应用或库项目中启用buildConfig,并添加了两个自定义的布尔类型的构建配置字段,用于表示新架构是否启用以及是否启用了 Hermes 引擎。
    		     * 这些字段将在生成的 BuildConfig 类中作为静态字段提供。
    		     * @param project Project
    		     * @param extension ReactExtension
    		     */
    		    fun configureBuildConfigFieldsForApp(project: Project, extension: ReactExtension) {
    		        val action =
    		            Action<AppliedPlugin> {
    		                project.extensions.getByType(AndroidComponentsExtension::class.java)
    		                    .finalizeDsl { ext ->
    		                        ext.buildFeatures.buildConfig = true
    		                        ext.defaultConfig.buildConfigField(
    		                            "boolean",
    		                            "IS_NEW_ARCHITECTURE_ENABLED",
    		                            project.isNewArchEnabled(extension).toString()
    		                        )
    		                        ext.defaultConfig.buildConfigField(
    		                            "boolean", "IS_HERMES_ENABLED", project.isHermesEnabled.toString()
    		                        )
    		                    }
    		            }
    		        project.pluginManager.withPlugin("com.android.application", action)
    		        project.pluginManager.withPlugin("com.android.library", action)
    		    }
    		
    
  • 配置开发端口 默认8081

    		       fun configureDevPorts(project: Project) {
    		        val devServerPort =
    		            project.properties["reactNativeDevServerPort"]?.toString() ?: DEFAULT_DEV_SERVER_PORT
    		        val inspectorProxyPort =
    		            project.properties["reactNativeInspectorProxyPort"]?.toString() ?: devServerPort
    		
    		        val action =
    		            Action<AppliedPlugin> {
    		                project.extensions.getByType(AndroidComponentsExtension::class.java)
    		                    .finalizeDsl { ext ->
    		                        ext.defaultConfig.resValue(
    		                            "integer",
    		                            "react_native_dev_server_port",
    		                            devServerPort
    		                        )
    		                        ext.defaultConfig.resValue(
    		                            "integer", "react_native_inspector_proxy_port", inspectorProxyPort
    		                        )
    		                    }
    		            }
    		
    		        project.pluginManager.withPlugin("com.android.application", action)
    		        project.pluginManager.withPlugin("com.android.library", action)
    		    }
    		
    
  • 处理老版本配置兼容性

     fun configureBackwardCompatibilityReactMap(project: Project) {
    if (project.extensions.extraProperties.has("react")) {
      @Suppress("UNCHECKED_CAST")
      val reactMap =
          project.extensions.extraProperties.get("react") as? Map<String, Any?> ?: mapOf()
      if (reactMap.isNotEmpty()) {
        project.logger.error(
            """
          ********************************************************************************
    
          ERROR: Using old project.ext.react configuration. 
          We identified that your project is using a old configuration block as:
          
          project.ext.react = [
              // ...
          ]
          
          You should migrate to the new configuration:
          
          react {
              // ...
          }
          You can find documentation inside `android/app/build.gradle` on how to use it.
        
          ********************************************************************************
          """
                .trimIndent())
      }
    }
    
    // We set an empty react[] map so if a library is reading it, they will find empty values.
    project.extensions.extraProperties.set("react", mapOf<String, String>())
    }
    
  • 配置Java工具链,确保项目中的 Java 和 Kotlin 代码使用 Java 17 版本

    fun configureJavaToolChains(input: Project) {
        // Check at the app level if react.internal.disableJavaVersionAlignment is set.
        if (input.hasProperty(INTERNAL_DISABLE_JAVA_VERSION_ALIGNMENT)) {
            return
        }
        input.rootProject.allprojects { project ->
            // Allows every single module to set react.internal.disableJavaVersionAlignment also.
            if (project.hasProperty(INTERNAL_DISABLE_JAVA_VERSION_ALIGNMENT)) {
                return@allprojects
            }
            val action =
                Action<AppliedPlugin> {
                    project.extensions.getByType(AndroidComponentsExtension::class.java)
                        .finalizeDsl { ext
                            ->
                            ext.compileOptions.sourceCompatibility = JavaVersion.VERSION_17
                            ext.compileOptions.targetCompatibility = JavaVersion.VERSION_17
                        }
                }
            project.pluginManager.withPlugin("com.android.application", action)
            project.pluginManager.withPlugin("com.android.library", action)
            project.pluginManager.withPlugin("org.jetbrains.kotlin.android") {
                project.extensions.getByType(KotlinTopLevelExtension::class.java).jvmToolchain(17)
            }
            project.pluginManager.withPlugin("org.jetbrains.kotlin.jvm") {
                project.extensions.getByType(KotlinTopLevelExtension::class.java).jvmToolchain(17)
            }
        }
    }
    
  • 根据不同的构建类型配置不同的任务

            //根据不同的构建类型配置不同的任务
            project.extensions.getByType(AndroidComponentsExtension::class.java).apply {
                onVariants(selector().all()) { variant ->
                    //配置react任务,用于执行react-native的打包操作
                    project.configureReactTasks(variant = variant, config = extension)
                }
            }
    

    configureReactTasks 扩展方法

       internal fun Project.configureReactTasks(variant: Variant, config: ReactExtension) {
        val targetName = variant.name.capitalizeCompat()
        val targetPath = variant.name
    
        val buildDir = this.layout.buildDirectory.get().asFile
        // Resources: generated/assets/react/<variant>/index.android.bundle
        val resourcesDir = File(buildDir, "generated/res/react/$targetPath")
        // Bundle: generated/assets/react/<variant>/index.android.bundle
        val jsBundleDir = File(buildDir, "generated/assets/react/$targetPath")
        // Sourcemap: generated/sourcemaps/react/<variant>/index.android.bundle.map
        val jsSourceMapsDir = File(buildDir, "generated/sourcemaps/react/$targetPath")
        // Intermediate packager:
        // intermediates/sourcemaps/react/<variant>/index.android.bundle.packager.map
        // Intermediate compiler:
        // intermediates/sourcemaps/react/<variant>/index.android.bundle.compiler.map
        val jsIntermediateSourceMapsDir = File(buildDir, "intermediates/sourcemaps/react/$targetPath")
    
        // The location of the cli.js file for React Native
        val cliFile = detectedCliFile(config)
    
        val isHermesEnabledInProject = project.isHermesEnabled
        val isHermesEnabledInThisVariant =
            if (config.enableHermesOnlyInVariants.get().isNotEmpty()) {
                config.enableHermesOnlyInVariants.get()
                    .contains(variant.name) && isHermesEnabledInProject
            } else {
                isHermesEnabledInProject
            }
        val isDebuggableVariant =
            config.debuggableVariants.get().any { it.equals(variant.name, ignoreCase = true) }
    
        //配置新架构打包选项
        configureNewArchPackagingOptions(project, config, variant)
        //配置JS引擎打包选项
        configureJsEnginePackagingOptions(config, variant, isHermesEnabledInThisVariant)
    
        if (!isDebuggableVariant) {
            val entryFileEnvVariable = System.getenv("ENTRY_FILE")
            val bundleTask =
                tasks.register("createBundle${targetName}JsAndAssets", BundleHermesCTask::class.java) {
                    it.root.set(config.root)
                    it.nodeExecutableAndArgs.set(config.nodeExecutableAndArgs)
                    it.cliFile.set(cliFile)
                    it.bundleCommand.set(config.bundleCommand)
                    it.entryFile.set(detectedEntryFile(config, entryFileEnvVariable))
                    it.extraPackagerArgs.set(config.extraPackagerArgs)
                    it.bundleConfig.set(config.bundleConfig)
                    it.bundleAssetName.set(config.bundleAssetName)
                    it.jsBundleDir.set(jsBundleDir)
                    it.resourcesDir.set(resourcesDir)
                    it.hermesEnabled.set(isHermesEnabledInThisVariant)
                    it.minifyEnabled.set(!isHermesEnabledInThisVariant)
                    it.devEnabled.set(false)
                    it.jsIntermediateSourceMapsDir.set(jsIntermediateSourceMapsDir)
                    it.jsSourceMapsDir.set(jsSourceMapsDir)
                    it.hermesCommand.set(config.hermesCommand)
                    it.hermesFlags.set(config.hermesFlags)
                    it.reactNativeDir.set(config.reactNativeDir)
                }
            //将生成的资源目录添加到源集
            variant.sources.res?.addGeneratedSourceDirectory(
                bundleTask,
                BundleHermesCTask::resourcesDir
            )
            variant.sources.assets?.addGeneratedSourceDirectory(
                bundleTask,
                BundleHermesCTask::jsBundleDir
            )
            }
        }
    
    
  • 配置react-native-codegen,用于生成所需代码,帮助我们避免编写重复代码的工具。

  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.srcFile
    
                                manifestFile
                                    .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 的作用是什么。


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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

多维时序 | Matlab实现BiGRU-Mutilhead-Attention双向门控循环单元融合多头注意力机制多变量时序预测

多维时序 | Matlab实现BiGRU-Mutilhead-Attention双向门控循环单元融合多头注意力机制多变量时序预测 目录 多维时序 | Matlab实现BiGRU-Mutilhead-Attention双向门控循环单元融合多头注意力机制多变量时序预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.多维时序 …

字典Trie树

字典树 : 概念 建字典树 查询 : 代码模板 : const int N100010; int n; char s[N]; int ch[N][26],cnt[N],idx;void insert(char *s){int p0;for(int i0; s[i]; i ){int js[i]-a;//字母映射if(!ch[p][j])ch[p][j]idx;pch[p][j];}cnt[p];//插入次数 } int query(char *s){i…

线上应用部署了两台load为1四核服务器

线上应用部署了两台服务器。 项目发布后&#xff0c;我对线上服务器的性能进行了跟踪&#xff0c;发现一台负载为3&#xff0c;另一台负载为1&#xff0c;其中一台四核服务器已经快到瓶颈了&#xff0c;所以我们紧急排查原因。 1、使用TOP命令查看占用CPU较大的负载和进程&…

《C语言都有哪些字符串处理函数?》

目录 17个字符串处理函数 1. gets()--读 2.fgets()--从指定文件内读 3.puts()--输出 4.fputs()--写入到指定文件中 5.strlen()--计算字符串长度 6.strcpy()--复制 7.strncpy()--复制前n个字符 8.strcat()--字符串连接 9.strncat()--将前n个字符连接 10.strcmp()--比…

Flink概述

1.什么是Flink 是一个框架和分布式处理引擎&#xff0c;用于对无界和有界数据流进行有状态计算。 官网&#xff1a;Flink 2.Flink的发展历史 Flink起源于一个叫作Stratosphere的项目&#xff0c;它是由3所地处柏林的大学和欧洲其他一些大学在2010~2014年共同进行的研究项目&a…

从零开始:神经网络(2)——MP模型

声明&#xff1a;本文章是根据网上资料&#xff0c;加上自己整理和理解而成&#xff0c;仅为记录自己学习的点点滴滴。可能有错误&#xff0c;欢迎大家指正。 神经元相关知识&#xff0c;详见从零开始&#xff1a;神经网络——神经元和梯度下降-CSDN博客 1、什么是M-P 模型 人…

物联网云原生云边协同

文章目录 一、物联网平台设计1.物联网平台设计2.物联网平台实现 二、部署环境1.节点配置2.版本信息 三、物联网平台部署1.部署 Kubernetes 集群2.部署 KubeEdge3.部署 ThingsBoard 集群4.部署 ThingsBoard Edge4.1.创建 Edge 实例4.2.部署 PostgreSQL4.3.创建数据库4.4.部署 Th…

构建高效可靠的消息队列系统:设计与实现

✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天开心哦&#xff01;✨✨ &#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; 目录 一、引言 二、设计目标 2.1、高可用性 1. 集群搭建 1.1 …

MACBOOK PRO M2 MAX 安装Stable Diffusion及文生图实例

以前偶尔会使用Midjourney生成一些图片&#xff0c;现在使用的头像就是当时花钱在Midjourney上生成的。前段时间从某鱼上拍了一台性价比还不错的macbook&#xff0c;想着不如自己部署Stable Diffusion&#xff08;以下简称SD&#xff09;尝试一下。 网上有很多教程&#xff0c…

数组的内存执行原理

一.Java内存分配介绍 JVM虚拟机会在内存中执行程序 java内存分配介绍 方法区&#xff0c;栈&#xff0c;堆 首先将编译过后的.class文件送入方法区中。当类开始运行时将方法调入栈内存中&#xff0c;变量也是属于方法的&#xff0c;因此同方法一起进入栈内存中。当main方法要…

日期问题---算法精讲

前言 今天讲讲日期问题&#xff0c;所谓日期问题&#xff0c;在蓝桥杯中出现众多&#xff0c;但是解法比较固定。 一般有判断日期合法性&#xff0c;判断是否闰年&#xff0c;判断日期的特殊形式&#xff08;回文或abababab型等&#xff09; 目录 例题 题2 题三 总结 …

人工智能|机器学习——K-means系列聚类算法k-means/ k-modes/ k-prototypes/ ......(划分聚类)

1.k-means聚类 1.1.算法简介 K-Means算法又称K均值算法&#xff0c;属于聚类&#xff08;clustering&#xff09;算法的一种&#xff0c;是应用最广泛的聚类算法之一。所谓聚类&#xff0c;即根据相似性原则&#xff0c;将具有较高相似度的数据对象划分至同一类簇&#xff0c;…

CentOS 7.6安装部署Seafile服务器

今天飞飞和你们分享CentOS 7.6上安装基于MySQL/MariaDB的Seafile服务器的方法&#xff0c;包括下载和安装7.0.5版本、配置数据库、启动服务器等步骤。安装成功后&#xff0c;需要通过nginx反向代理才能访问seafile服务。 通过预编译好的安装包来安装并运行基于 MySQL/MariaDB …

Day27:安全开发-PHP应用TP框架路由访问对象操作内置过滤绕过核心漏洞

目录 TP框架-开发-配置架构&路由&MVC模型 TP框架-安全-不安全写法&版本过滤绕过 思维导图 PHP知识点 功能&#xff1a;新闻列表&#xff0c;会员中心&#xff0c;资源下载&#xff0c;留言版&#xff0c;后台模块&#xff0c;模版引用&#xff0c;框架开发等 技…

网络触手获取天气数据存入mysql 项目

首先这个案例不一定能直接拿来用&#xff0c;虽然我觉得可以但是里面肯定有一些我没考虑到的地方。 有问题评论或者私信我&#xff1a; 这个案例适合我这种学生小白 获取天气数据网址&#xff1a; https://lishi.tianqi.com/xianyang/202201.html 网络触手获取天气数据代码直…

Svg Flow Editor 原生svg流程图编辑器(二)

系列文章 Svg Flow Editor 原生svg流程图编辑器&#xff08;一&#xff09; 说明 这项目也是我第一次写TS代码哈&#xff0c;现在还被绕在类型中头昏脑胀&#xff0c;更新可能会慢点&#xff0c;大家见谅~ 目前实现的功能&#xff1a;1. 元件的创建、移动、形变&#xff1b;2…

运动想象 (MI) 迁移学习系列 (3) : MSFT

运动想象迁移学习系列:MSFT 0. 引言1. 主要贡献2. 数据增强方法3. 基于度量的空间滤波转换器3.1 空间过滤3.2 脑电图ViT3.2.1 变压器编码器层3.2.2 基于度量的损失函数 4. 实验结果4.1 消融实验4.2 基线任务对比4.3 跨主题 5. 总结欢迎来稿 论文地址&#xff1a;https://www.s…

深入浅出计算机网络 day.1 概论② 因特网概述

当你回头看的时候&#xff0c;你会发现自己走了一段&#xff0c;自己都没想到的路 —— 24.3.9 内容概述 01.网络、互连&#xff08;联&#xff09;网与因特网的区别与联系 02.因特网简介 一、网络、互连&#xff08;联&#xff09;网与因特网的区别与联系 1.若干节点和链路互连…

Java 客户端向服务端上传文件(TCP通信)

一、实验内容 编写一个客户端向服务端上传文件的程序&#xff0c;要求使用TCP通信的的知识&#xff0c;完成将本地机器输入的路径下的文件上传到D盘中名称为upload的文件夹中。并把客户端的IP地址加上count标识作为上传后文件的文件名&#xff0c;即IP&#xff08;count&#…

excel统计分析——嵌套设计

参考资料&#xff1a;生物统计学&#xff0c;巢式嵌套设计的方差分析 嵌套设计&#xff08;nested design&#xff09;也称为系统分组设计或巢式设计&#xff0c;是把试验空间逐级向低层次划分的试验设计方法。与裂区设计相似&#xff0c;先按一级因素设计试验&#xff0c;然后…