首页 > 代码库 > React Native Android Gradle 编译流程浅析

React Native Android Gradle 编译流程浅析

【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我】

1 背景

前面已经发车了一篇《React Native Android 从学车到补胎和成功发车经历》,接着就该好好琢磨一下 React Native 周边了,没看第一篇的可以先去看看;这里我们先从 React Native 的 Android 编译来简单揭晓一下 React Native 在集成的过程中到底干了哪些不可告人的坏事;由于我们项目准备以 Gradle 形式接入,加上对 Gradle 比 BUCK 熟悉的多,所以本文就来分析 RN Gradle 的编译流程;至于 BUCK 编译,后面有时间了再研究下写一篇吧。唉,市面上 RN 的文章都烂大街了,除过几个给力的厂子分享的文章外,大多数个人博客关于 RN 文章都是简单的控件使用或者官方文档翻译,想说的是,RN 那些文档是不够的,自己接入时才会发现很多问题需要自己棘手处理,所以还是要靠自己。

PS:如果你对 gradle 不熟悉的话不妨先去看看我 15 年写的两篇入门文章 《Groovy脚本基础全攻略》 、《Gradle脚本基础全攻略》,否则接下来的内容可能看起来会很吃力。

【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我】

2 RN 直接集成引用编译浅析

还记得依照官方集成 RN 的步骤吗,首先在项目最外层的 build.gradle 添加了如下代码:

allprojects {
    repositories {
        ......
        maven {
            //指向本地一个仓库路径
            url "$rootDir/../node_modules/react-native/android"
        }
    }
}

然后在 app 的 build.gradle 中进行依赖配置(版本号使用 + 即可),就这样就完事了,你可能会比较好奇这个过程吧,下面就来仔细分析这种引用方式下的编译流程。

1、首先 npm install 时会依据 package.json 的依赖配置下载安装 node_modules 里面的相关模块。
2、完事打开 $rootDir/../node_modules/react-native/android 目录你会发现下面竟然是个本地的 maven 仓库,具体的本地 maven 仓库元数据文件 maven-metadata.xml 内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<metadata>
  <groupId>com.facebook.react</groupId>
  <artifactId>react-native</artifactId>
  <versioning>
    <release>0.33.0</release>
    <versions>
      <version>0.33.0</version>
    </versions>
    <lastUpdated>20160909144548</lastUpdated>
  </versioning>
</metadata>

握草!这不就是活生生的 maven 仓库索引源文件吗,平级目录下还有同名不同后缀的 md5、sha1 校验文件,还有相关的 aar 包、jar 包等仓库数据源。好家伙!原来直接引用 RN 依赖本地仓库编译就是这么简单,该有的仓库坐标全给你了,不过有人之前问了,那 app 中 build.gradle 配置的依赖 react-native 为啥这样配置以后就不去下载远程 maven 仓库的了,而是使用了本地的呢?关于这个问题我只想说你得补习 android 基础了,不信你跳个坑就明白了,怎么跳呢,如下:

  • 假设 package.json 中依赖版本为 0.33.0。
  • 接着不要修改 project 下配置的本地 maven 仓库路径,同时保证本地 maven 仓库不动。
  • 这时候修改 app 下 build.gradle 文件中 react-native 依赖版本为非 “+” 和非 0.33.0 版本,然后编译运行看看。

你会发现运行的 RN 版本不是 0.33.0 的,也就是说同样的写法只是修改了依赖的版本号为不对应本地 maven 仓库的以后 gradle 就聪明的使用了远程仓库的 aar 包。哈哈,是这样的,因为 maven 会优先使用本地仓库索引哇。

接着你要是不想在 release 版本中(集成 RN 到现有 project 需要,直接 init 的默认就有如下配置)通过命令手动生成 bundle 和资源文件的话,你需要在 app 的 build.gradle 文件上面添加一个 gradle 自动打包的 task 文件,这个文件 RN 团队已经帮忙写好了,我们要做的事集成和添加配置即可,具体如下:

//注意目录修改为你项目组织的路径
project.ext.react = [
        root: "../react-native/",
]
apply from: "../react-native/node_modules/react-native/react.gradle"

呦西,RN 还是挺体贴的,react.gradle 都给准备好了,那我们下面就看看这个文件里都是啥玩意呗,如下:

//引用ant.jar的Os类,便于下面进行cmd环境判断Os.isFamily(Os.FAMILY_WINDOWS)
import org.apache.tools.ant.taskdefs.condition.Os

//判断在apply这段脚本前project.ext有没有配置react属性,默认是没有的,可以依据自己项目配置下面列出的相关属性
def config = project.hasProperty("react") ? project.react : [];
//获取相关名字,默认即可,不用配
def bundleAssetName = config.bundleAssetName ?: "index.android.bundle"
def entryFile = config.entryFile ?: "index.android.js"

// because elvis operator
def elvisFile(thing) {
    return thing ? file(thing) : null;
}
//react根目录,依据自己项目结构配置路径
def reactRoot = elvisFile(config.root) ?: file("../../")
//编译时过滤哪些目录
def inputExcludes = config.inputExcludes ?: ["android/**", "ios/**"]
//一个公用方法,dependentTaskName依赖于task执行
void runBefore(String dependentTaskName, Task task) {
    Task dependentTask = tasks.findByPath(dependentTaskName);
    if (dependentTask != null) {
        dependentTask.dependsOn task
    }
}

//项目配置好,准备执行前要跑的一个闭包
gradle.projectsEvaluated {
    // Grab all build types and product flavors
    def buildTypes = android.buildTypes.collect { type -> type.name }
    def productFlavors = android.productFlavors.collect { flavor -> flavor.name }

    // When no product flavors defined, use empty
    if (!productFlavors) productFlavors.add(‘‘)
    //buildTypes与productFlavors二重循环遍历操作
    productFlavors.each { productFlavorName ->
        buildTypes.each { buildTypeName ->
            //获取拼接相关各种 bundle、assets资源路径,和原有app build路径合并,方便打包task自动合并到apk中
            // Create variant and target names
            def flavorNameCapitalized = "${productFlavorName.capitalize()}"
            def buildNameCapitalized = "${buildTypeName.capitalize()}"
            def targetName = "${flavorNameCapitalized}${buildNameCapitalized}"
            def targetPath = productFlavorName ?
                    "${productFlavorName}/${buildTypeName}" :
                    "${buildTypeName}"

            // React js bundle directories
            def jsBundleDirConfigName = "jsBundleDir${targetName}"
            def jsBundleDir = elvisFile(config."$jsBundleDirConfigName") ?:
                    file("$buildDir/intermediates/assets/${targetPath}")

            def resourcesDirConfigName = "resourcesDir${targetName}"
            def resourcesDir = elvisFile(config."${resourcesDirConfigName}") ?:
                    file("$buildDir/intermediates/res/merged/${targetPath}")
            def jsBundleFile = file("$jsBundleDir/$bundleAssetName")

            // Bundle task name for variant
            def bundleJsAndAssetsTaskName = "bundle${targetName}JsAndAssets"

            // Additional node and packager commandline arguments
            def nodeExecutableAndArgs = config.nodeExecutableAndArgs ?: ["node"]
            def extraPackagerArgs = config.extraPackagerArgs ?: []
            //创建一个bundle${targetName}JsAndAssets的task备用(该脚本的核心,实质就是执行我们手动的bundle打包命令)
            def currentBundleTask = tasks.create(
                    name: bundleJsAndAssetsTaskName,
                    type: Exec) {
                group = "react"
                description = "bundle JS and assets for ${targetName}."

                // Create dirs if they are not there (e.g. the "clean" task just ran)
                doFirst {
                    jsBundleDir.mkdirs()
                    resourcesDir.mkdirs()
                }

                // Set up inputs and outputs so gradle can cache the result
                inputs.files fileTree(dir: reactRoot, excludes: inputExcludes)
                outputs.dir jsBundleDir
                outputs.dir resourcesDir

                // Set up the call to the react-native cli
                workingDir reactRoot

                // Set up dev mode
                def devEnabled = !targetName.toLowerCase().contains("release")
                //执行bundle、assets打包到指定路径(和手动执行一样的命令)
                if (Os.isFamily(Os.FAMILY_WINDOWS)) {
                    commandLine("cmd", "/c", *nodeExecutableAndArgs, "node_modules/react-native/local-cli/cli.js", "bundle", "--platform", "android", "--dev", "${devEnabled}",
                            "--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir, *extraPackagerArgs)
                } else {
                    commandLine(*nodeExecutableAndArgs, "node_modules/react-native/local-cli/cli.js", "bundle", "--platform", "android", "--dev", "${devEnabled}",
                            "--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir, *extraPackagerArgs)
                }
                //依据外面project.ext有没有配置react的bundleIn${targetName}=true或者是不是release模式决定当前task是否enabled
                enabled config."bundleIn${targetName}" ||
                    config."bundleIn${buildTypeName.capitalize()}" ?:
                            targetName.toLowerCase().contains("release")
            }
            //保证currentBundleTask在merge${targetName}Resources和merge${targetName}Assets之后执行
            // Hook bundle${productFlavor}${buildType}JsAndAssets into the android build process
            currentBundleTask.dependsOn("merge${targetName}Resources")
            currentBundleTask.dependsOn("merge${targetName}Assets")
            //保证currentBundleTask在如下runBefore方法指定的第一个参数的task之前执行(这样bundle和assets就准备好了,方便后续打入apk)
            runBefore("process${flavorNameCapitalized}Armeabi-v7a${buildNameCapitalized}Resources", currentBundleTask)
            runBefore("process${flavorNameCapitalized}X86${buildNameCapitalized}Resources", currentBundleTask)
            runBefore("processUniversal${targetName}Resources", currentBundleTask)
            runBefore("process${targetName}Resources", currentBundleTask)
        }
    }
}

怎么样,整体看这种集成的编译和普通 Android 项目 Gradle 编译没啥区别吧,所以就不多说了。给上一张图直观自己体会吧:
技术分享

PS一个小插曲:

  1. 由于项目太大、太复杂、太久远,所以之前是 ant 编译,现在在迁移 gradle 的路上,集成 RN 也是同步进行的,结果被别人的 gradle 坑了一把,那就是为了区分与 ant 的 build 目录冲突,重新设置了 gradle 的输出目录,但是估计当时写脚本的人误把 buildDir=”gradleBuild” 写在了 app 的 build.gradle 中,导致最终引入 react.gradle task 以后每次成功编译出 release.apk 中总是没有 bundle 和 RN res 资源,我去,当时没留意别人写的 buildDir 赋值,一直以为是自己修改 react.gradle 出问题了,各种打印,后来没辙了,对比执行了 gradle properties 才发现好坑爹,buildDir 怎么还是 build 目录,一看才发现 buildDir 在 app 的 gradle 文件头赋值的。。。。。。。坑爹啊,应该针对整个 project 哇,果断换到了根目录的 allprojects 闭包中赋值,重新编译就打进去了。古人的坑哇。。。

  2. 用官方 init 创建工程可能没事,自己集成 RN 的 react.gradle 到现有项目你可能也会遇到这个坑爹的【issues#5787】 问题,intermediates 输出的 RN drawable 资源名字诡异坑爹,自己要多留意下,我反正当时被坑住了。

【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我】

3 RN 源码 gradle 编译浅析

这块是我们这篇的主要核心。当我们将 RN 以源码方式集成时就会涉及到 RN 源码的编译流程,这个黑盒子到底是怎么个流程呢?下面我们就来看看吧。

首先按照官方以 module 形式引入 RN 源码,然后你会看到实质引入的是 ReactAndroid 工程,关于这个源码工程和依赖的主要项目截图如下:
技术分享
那就按照惯例去看看这个核心工程的 build.gradle 脚本呗(比较长,慎重),如下:

// Copyright 2015-present Facebook. All Rights Reserved.
//表明编译为android lib
apply plugin: ‘com.android.library‘
apply plugin: ‘maven‘

apply plugin: ‘de.undercouch.download‘
//一些外部包引入
import de.undercouch.gradle.tasks.download.Download
import org.apache.tools.ant.taskdefs.condition.Os
import org.apache.tools.ant.filters.ReplaceTokens

// We download various C++ open-source dependencies into downloads.
// We then copy both the downloaded code and our custom makefiles and headers into third-party-ndk.
// After that we build native code from src/main/jni with module path pointing at third-party-ndk.
//定义好下载目录和第三方ndk目录
def downloadsDir = new File("$buildDir/downloads")
def thirdPartyNdkDir = new File("$buildDir/third-party-ndk")

// The Boost library is a very large download (>100MB).
// If Boost is already present on your system, define the REACT_NATIVE_BOOST_PATH env variable
// and the build will use that.
//英文注释很明白了,就是为了避免重复
def boostPath = System.getenv("REACT_NATIVE_BOOST_PATH")
//定义创建目录的task
task createNativeDepsDirectories {
    downloadsDir.mkdirs()
    thirdPartyNdkDir.mkdirs()
}
//定义一个下载Boost C++扩展库的task,依赖于createNativeDepsDirectories task执行
task downloadBoost(dependsOn: createNativeDepsDirectories, type: Download) {
    // Use ZIP version as it‘s faster this way to selectively extract some parts of the archive
    src ‘https://downloads.sourceforge.net/project/boost/boost/1.57.0/boost_1_57_0.zip‘
    // alternative
    // src ‘http://mirror.nienbo.com/boost/boost_1_57_0.zip‘
    onlyIfNewer true
    overwrite false
    //下载zip到已经创建好的downloadsDir目录下,起名字为boost_1_57_0.zip
    dest new File(downloadsDir, ‘boost_1_57_0.zip‘)
}
//定义一个Boost文件拷贝的的task,当已经Boost了就不依赖上面下载的task了,第一次下下来则依赖于downloadBoost task执行
task prepareBoost(dependsOn: boostPath ? [] : [downloadBoost], type: Copy) {
    //第一次的话就解压downloadsDir目录下的boost_1_57_0.zip
    from boostPath ? boostPath : zipTree(downloadBoost.dest)
    from ‘src/main/jni/third-party/boost/Android.mk‘
    include ‘boost_1_57_0/boost/**/*.hpp‘, ‘Android.mk‘
    //把上面from、include指定的相关文件全部copy到thirdPartyNdkDir的boost目录下,为后续编译做文件准备
    into "$thirdPartyNdkDir/boost"
}
//定义一个DoubleConversion C++拓展库下载的task,依赖于createNativeDepsDirectories task执行
task downloadDoubleConversion(dependsOn: createNativeDepsDirectories, type: Download) {
    src ‘https://github.com/google/double-conversion/archive/v1.1.1.tar.gz‘
    onlyIfNewer true
    overwrite false
    //下载tar.gz到已经创建好的downloadsDir目录下,起名字为double-conversion-1.1.1.tar.gz
    dest new File(downloadsDir, ‘double-conversion-1.1.1.tar.gz‘)
}
//定义一个DoubleConversion文件拷贝的的task,依赖于downloadDoubleConversion task执行
task prepareDoubleConversion(dependsOn: downloadDoubleConversion, type: Copy) {
    from tarTree(downloadDoubleConversion.dest)
    from ‘src/main/jni/third-party/double-conversion/Android.mk‘
    include ‘double-conversion-1.1.1/src/**/*‘, ‘Android.mk‘
    filesMatching(‘*/src/**/*‘, {fname -> fname.path = "double-conversion/${fname.name}"})
    includeEmptyDirs = false
    //把上面from、include指定的相关文件全部copy到thirdPartyNdkDir的double-conversion目录下,为后续编译做文件准备
    into "$thirdPartyNdkDir/double-conversion"
}
//定义一个Folly下载的task,依赖于createNativeDepsDirectories task执行
task downloadFolly(dependsOn: createNativeDepsDirectories, type: Download) {
    src ‘https://github.com/facebook/folly/archive/deprecate-dynamic-initializer.tar.gz‘
    onlyIfNewer true
    overwrite false
    //和上面下载task类似。。。。。不多说了
    dest new File(downloadsDir, ‘folly-deprecate-dynamic-initializer.tar.gz‘);
}
//和上面类似。。。。。不多说了
task prepareFolly(dependsOn: downloadFolly, type: Copy) {
    from tarTree(downloadFolly.dest)
    from ‘src/main/jni/third-party/folly/Android.mk‘
    include ‘folly-deprecate-dynamic-initializer/folly/**/*‘, ‘Android.mk‘
    eachFile {fname -> fname.path = (fname.path - "folly-deprecate-dynamic-initializer/")}
    includeEmptyDirs = false
    //和上面类似。。。。。不多说了,就是复制src/main/jni/third-party/下相关mk和下载下来文件
    into "$thirdPartyNdkDir/folly"
}
//和上面类似。。。。。不多说了
task downloadGlog(dependsOn: createNativeDepsDirectories, type: Download) {
    src ‘https://github.com/google/glog/archive/v0.3.3.tar.gz‘
    onlyIfNewer true
    overwrite false
    dest new File(downloadsDir, ‘glog-0.3.3.tar.gz‘)
}

// Prepare glog sources to be compiled, this task will perform steps that normally should‘ve been
// executed by automake. This way we can avoid dependencies on make/automake
//和上面类似。。。。。不多说了
task prepareGlog(dependsOn: downloadGlog, type: Copy) {
    from tarTree(downloadGlog.dest)
    from ‘src/main/jni/third-party/glog/‘
    include ‘glog-0.3.3/src/**/*‘, ‘Android.mk‘, ‘config.h‘
    includeEmptyDirs = false
    filesMatching(‘**/*.h.in‘) {
        filter(ReplaceTokens, tokens: [
                ac_cv_have_unistd_h: ‘1‘,
                ac_cv_have_stdint_h: ‘1‘,
                ac_cv_have_systypes_h: ‘1‘,
                ac_cv_have_inttypes_h: ‘1‘,
                ac_cv_have_libgflags: ‘0‘,
                ac_google_start_namespace: ‘namespace google {‘,
                ac_cv_have_uint16_t: ‘1‘,
                ac_cv_have_u_int16_t: ‘1‘,
                ac_cv_have___uint16: ‘0‘,
                ac_google_end_namespace: ‘}‘,
                ac_cv_have___builtin_expect: ‘1‘,
                ac_google_namespace: ‘google‘,
                ac_cv___attribute___noinline: ‘__attribute__ ((noinline))‘,
                ac_cv___attribute___noreturn: ‘__attribute__ ((noreturn))‘,
                ac_cv___attribute___printf_4_5: ‘__attribute__((__format__ (__printf__, 4, 5)))‘
        ])
        it.path = (it.name - ‘.in‘)
    }
    into "$thirdPartyNdkDir/glog"
}
//和上面类似。。。。。不多说了,唯一区别就是去指定的jscAPIBaseURL路径下挑了几个.h文件下载下来而已
task downloadJSCHeaders(type: Download) {
    def jscAPIBaseURL = ‘https://svn.webkit.org/repository/webkit/!svn/bc/174650/trunk/Source/JavaScriptCore/API/‘
    def jscHeaderFiles = [‘JavaScript.h‘, ‘JSBase.h‘, ‘JSContextRef.h‘, ‘JSObjectRef.h‘, ‘JSRetainPtr.h‘, ‘JSStringRef.h‘, ‘JSValueRef.h‘, ‘WebKitAvailability.h‘]
    def output = new File(downloadsDir, ‘jsc‘)
    output.mkdirs()
    src(jscHeaderFiles.collect { headerName -> "$jscAPIBaseURL$headerName" })
    onlyIfNewer true
    overwrite false
    dest output
}

// Create Android.mk library module based on so files from mvn + include headers fetched from webkit.org
//和上面类似。。。。。不多说了,唯一区别就是复制的有一部分so等东西是来自dependencies里compile依赖的android-jsc aar而已(aar里没有armeabi的so,坑爹啊)
task prepareJSC(dependsOn: downloadJSCHeaders) << {
    copy {
        from zipTree(configurations.compile.fileCollection { dep -> dep.name == ‘android-jsc‘ }.singleFile)
        from {downloadJSCHeaders.dest}
        from ‘src/main/jni/third-party/jsc/Android.mk‘
        include ‘jni/**/*.so‘, ‘*.h‘, ‘Android.mk‘
        filesMatching(‘*.h‘, { fname -> fname.path = "JavaScriptCore/${fname.path}"})
        into "$thirdPartyNdkDir/jsc";
    }
}
//定义方法依据平台决定 NDK build 的命令是调用哪个环境的脚本
def getNdkBuildName() {
    if (Os.isFamily(Os.FAMILY_WINDOWS)) {
        return "ndk-build.cmd"
    } else {
        return "ndk-build"
    }
}
//定义方法判断查找ndk路径
def findNdkBuildFullPath() {
    // we allow to provide full path to ndk-build tool
    if (hasProperty(‘ndk.command‘)) {
        return property(‘ndk.command‘)
    }
    // or just a path to the containing directory
    if (hasProperty(‘ndk.path‘)) {
        def ndkDir = property(‘ndk.path‘)
        return new File(ndkDir, getNdkBuildName()).getAbsolutePath()
    }
    if (System.getenv(‘ANDROID_NDK‘) != null) {
        def ndkDir = System.getenv(‘ANDROID_NDK‘)
        return new File(ndkDir, getNdkBuildName()).getAbsolutePath()
    }
    def ndkDir = android.hasProperty(‘plugin‘) ? android.plugin.ndkFolder :
            plugins.getPlugin(‘com.android.library‘).sdkHandler.getNdkFolder()
    if (ndkDir) {
        return new File(ndkDir, getNdkBuildName()).getAbsolutePath()
    }
    return null
}
//定义方法获取ndk路径
def getNdkBuildFullPath() {
    def ndkBuildFullPath = findNdkBuildFullPath()
    if (ndkBuildFullPath == null) {
        throw new GradleScriptException(
            "ndk-build binary cannot be found, check if you‘ve set " +
            "\$ANDROID_NDK environment variable correctly or if ndk.dir is " +
            "setup in local.properties",
            null)
    }
    if (!new File(ndkBuildFullPath).canExecute()) {
        throw new GradleScriptException(
            "ndk-build binary " + ndkBuildFullPath + " doesn‘t exist or isn‘t executable.\n" +
            "Check that the \$ANDROID_NDK environment variable, or ndk.dir in local.proerties, is set correctly.\n" +
            "(On Windows, make sure you escape backslashes in local.properties or use forward slashes, e.g. C:\\\\ndk or C:/ndk rather than C:\\ndk)",
            null)
    }
    return ndkBuildFullPath
}
//定义憋大招的buildReactNdkLib task,以来上面所有下载拷贝的task,把他们全部进行NDK编译。。。。。。
task buildReactNdkLib(dependsOn: [prepareJSC, prepareBoost, prepareDoubleConversion, prepareFolly, prepareGlog], type: Exec) {
    inputs.file(‘src/main/jni/xreact‘)
    outputs.dir("$buildDir/react-ndk/all")
    //就是常规的ndk编译命令咯,指定了ABI等的Application.mk、相关模块的mk、和相关要编译的源码目录
    commandLine getNdkBuildFullPath(),
            ‘NDK_PROJECT_PATH=null‘,
            "NDK_APPLICATION_MK=$projectDir/src/main/jni/Application.mk",
            ‘NDK_OUT=‘ + temporaryDir,
            "NDK_LIBS_OUT=$buildDir/react-ndk/all",
            "THIRD_PARTY_NDK_DIR=$buildDir/third-party-ndk",
            "REACT_COMMON_DIR=$projectDir/../ReactCommon",
            ‘-C‘, file(‘src/main/jni/react/jni‘).absolutePath,
            ‘--jobs‘, project.hasProperty("jobs") ? project.property("jobs") : Runtime.runtime.availableProcessors()
}
//清除ndk编译的task
task cleanReactNdkLib(type: Exec) {
    commandLine getNdkBuildFullPath(),
            "NDK_APPLICATION_MK=$projectDir/src/main/jni/Application.mk",
            "THIRD_PARTY_NDK_DIR=$buildDir/third-party-ndk",
            ‘-C‘, file(‘src/main/jni/react/jni‘).absolutePath,
            ‘clean‘
}
//创建复制ndk lib task,依赖buildReactNdkLib
task packageReactNdkLibs(dependsOn: buildReactNdkLib, type: Copy) {
    from "$buildDir/react-ndk/all"
    exclude ‘**/libjsc.so‘
    into "$buildDir/react-ndk/exported"
}
//给BUCK编译使用的task
task packageReactNdkLibsForBuck(dependsOn: packageReactNdkLibs, type: Copy) {
  from "$buildDir/react-ndk/exported"
  into "src/main/jni/prebuilt/lib"
}
//android闭包,和常见的Android工程没啥区别
android {
    compileSdkVersion 23
    buildToolsVersion "23.0.1"

    defaultConfig {
        minSdkVersion 16
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"

        ndk {
            moduleName "reactnativejni"
        }

        buildConfigField ‘boolean‘, ‘IS_INTERNAL_BUILD‘, ‘false‘
        buildConfigField ‘int‘, ‘EXOPACKAGE_FLAGS‘, ‘0‘
        testApplicationId "com.facebook.react.tests.gradle"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }

    sourceSets.main {
        jni.srcDirs = []
        jniLibs.srcDir "$buildDir/react-ndk/exported"
        res.srcDirs = [‘src/main/res/devsupport‘, ‘src/main/res/shell‘, ‘src/main/res/views/modal‘]
        java {
          srcDirs = [‘src/main/java‘, ‘src/main/libraries/soloader/java‘, ‘src/main/jni/first-party/fb/jni/java‘]
          exclude ‘com/facebook/react/processing‘
        }
    }
    //JavaCompile编译之前保证执行完了packageReactNdkLibs
    tasks.withType(JavaCompile) {
        compileTask -> compileTask.dependsOn packageReactNdkLibs
    }

    clean.dependsOn cleanReactNdkLib

    lintOptions {
        abortOnError false
    }
    packagingOptions {
        exclude ‘META-INF/NOTICE‘
        exclude ‘META-INF/LICENSE‘
    }
}
//一堆依赖,和常见的Android工程没啥区别,RN大的原因一方面是so库,还有一方面就是这里导致的,有能力的团队可以裁减掉这里一些东东
dependencies {
    compile fileTree(dir: ‘src/main/third-party/java/infer-annotations/‘, include: [‘*.jar‘])
    compile ‘javax.inject:javax.inject:1‘
    compile ‘com.android.support:appcompat-v7:23.0.1‘
    compile ‘com.android.support:recyclerview-v7:23.0.1‘
    compile ‘com.facebook.fresco:fresco:0.11.0‘
    compile ‘com.facebook.fresco:imagepipeline-okhttp3:0.11.0‘
    compile ‘com.facebook.soloader:soloader:0.1.0‘
    compile ‘com.fasterxml.jackson.core:jackson-core:2.2.3‘
    compile ‘com.google.code.findbugs:jsr305:3.0.0‘
    compile ‘com.squareup.okhttp3:okhttp:3.4.1‘
    compile ‘com.squareup.okhttp3:okhttp-urlconnection:3.4.1‘
    compile ‘com.squareup.okhttp3:okhttp-ws:3.4.1‘
    compile ‘com.squareup.okio:okio:1.9.0‘
    compile ‘org.webkit:android-jsc:r174650‘

    testCompile "junit:junit:${JUNIT_VERSION}"
    testCompile "org.powermock:powermock-api-mockito:${POWERMOCK_VERSION}"
    testCompile ‘com.fasterxml.jackson.core:jackson-databind:2.2.3‘
    testCompile "org.powermock:powermock-module-junit4-rule:${POWERMOCK_VERSION}"
    testCompile "org.powermock:powermock-classloading-xstream:${POWERMOCK_VERSION}"
    testCompile "org.mockito:mockito-core:${MOCKITO_CORE_VERSION}"
    testCompile "org.easytesting:fest-assert-core:${FEST_ASSERT_CORE_VERSION}"
    testCompile "org.robolectric:robolectric:${ROBOLECTRIC_VERSION}"

    androidTestCompile fileTree(dir: ‘src/main/third-party/java/buck-android-support/‘, include: [‘*.jar‘])
    androidTestCompile ‘com.android.support.test:runner:0.3‘
    androidTestCompile "org.mockito:mockito-core:${MOCKITO_CORE_VERSION}"
}
//build.gradle又引入了另一个脚本文件,可以说和编译没关系的,都是些发布编译包到仓库的例行脚本。
//发布到远程仓库或者本地上面主题2分析的本地 maven 仓库(android目录下)。
apply from: ‘release.gradle‘

可以看见,其实没啥的,就是比普通 gradle 多了一些 task 而已,核心都是为了服务 NDK 编译相关的源码处理等,其他的和普通 Android 工程 lib 编译没区别的;上面所处理的 NDK 编译最后的核心是指定的那个 Application.mk 和各个目录下自己的 Android.mk,这明显了吧,这都是 Android 源码里编译的常规配置,也是 NDK 的基础,不多说明了,只是提醒一点,Application.mk 是这样的:

APP_BUILD_SCRIPT := Android.mk
//只编译输出了armeabi-v7a x86的ABI so
APP_ABI := armeabi-v7a x86
//使用android-9,保证了规避NDK臭名昭著的新版本编译不向前低版本兼容问题,低版本在新版本兼容的特点
APP_PLATFORM := android-9

APP_MK_DIR := $(dir $(lastword $(MAKEFILE_LIST)))

NDK_MODULE_PATH := $(APP_MK_DIR)$(HOST_DIRSEP)$(THIRD_PARTY_NDK_DIR)$(HOST_DIRSEP)$(REACT_COMMON_DIR)$(HOST_DIRSEP)$(APP_MK_DIR)first-party

APP_STL := gnustl_shared

# Make sure every shared lib includes a .note.gnu.build-id header
APP_LDFLAGS := -Wl,--build-id

NDK_TOOLCHAIN_VERSION := 4.8

最后就是那个 apply release.gradle 了,没啥多说的,发布包到仓库而已,发布到远程或者本地仓库;就这样神奇的 React Native 源码 gradle 脚本编译就实现了。下面我们例行我的博客风格惯例,枯燥的源码整理成一张简单的图,记住这张图就明白 RN 整个编译过程干了些啥 BB 事了。如下:

技术分享

图片被 CSDN 搞模糊了,想看详细的右键打开新页面就能看到清晰的了。

【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我】

5 总结

怎么说呢?了解 React Native 的编译流程是进行 React Native 裁剪阉割的首要任务,理解编译流程才能去看如何依赖、如何裁剪,这和 Android 源码一样,你想修改的前提是熟悉整个 Android 系统源码 build 目录下的 各种 shell、python 脚本的大致框架流程吧,否则搞毛线。

怎么样,在没接触 React Native 之前总觉得 RN 很神奇,随着这一系列第二篇文章的诞生,你有没有感觉到 RN 的面纱正在被解开的路上(虽然只是开始,源码才是重点。。。)。关于源码分析和 BUCK 编译(我得抽空再研究下 BUCK,BUCK 只能在 Linux 和 Mac 上用,对我们项目其实没啥作用的。。。)后面再写文章分析吧。

PPPS一句,特别狗血的忽略点:

在 ReactAndroid 工程下的 AndroidManifest.xml 里是这样的:

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.facebook.react">

    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

    <application />

</manifest>

如果集成 RN 以后,你的应用对添加权限比较在意的情况下还是想办法在 release 版本中把这个权限干掉吧,这个是服务 debug 版本调试的,release 切记别直接带出去了。

那就这样吧,曲终人散。。。。。明天还有事要处理。。。。。。。

技术分享
【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我】

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    React Native Android Gradle 编译流程浅析