React Native Android Gradle 编译流程浅析

来源:互联网 发布:西门子plc编程软件下载 编辑:程序博客网 时间:2024/06/08 09:48

【工匠若水 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 operatordef 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 libapply plugin: 'com.android.library'apply plugin: 'maven'apply plugin: 'de.undercouch.download'//一些外部包引入import de.undercouch.gradle.tasks.download.Downloadimport org.apache.tools.ant.taskdefs.condition.Osimport 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")//定义创建目录的tasktask 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编译的tasktask 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,依赖buildReactNdkLibtask packageReactNdkLibs(dependsOn: buildReactNdkLib, type: Copy) {    from "$buildDir/react-ndk/all"    exclude '**/libjsc.so'    into "$buildDir/react-ndk/exported"}//给BUCK编译使用的tasktask 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 soAPP_ABI := armeabi-v7a x86//使用android-9,保证了规避NDK臭名昭著的新版本编译不向前低版本兼容问题,低版本在新版本兼容的特点APP_PLATFORM := android-9APP_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-partyAPP_STL := gnustl_shared# Make sure every shared lib includes a .note.gnu.build-id headerAPP_LDFLAGS := -Wl,--build-idNDK_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 未经允许严禁转载,请尊重作者劳动成果。私信联系我】

5 0