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一个小插曲:
由于项目太大、太复杂、太久远,所以之前是 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 闭包中赋值,重新编译就打进去了。古人的坑哇。。。
用官方 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 未经允许严禁转载,请尊重作者劳动成果。私信联系我】
- React Native Android Gradle 编译流程浅析
- React Native Android Gradle 编译流程浅析(一)
- React Native Android Gradle 编译流程浅析(二)
- 深度解析Gradle编译React native时遇到的那些坑【适用于Android开发者】
- Android React Native自定义组件的流程
- React Native 源码浅析
- React Native 编译Android项目问题整理
- React Native Android 创建新项目编译
- react-native android编译报错
- 解决 react-native run-android 出现 gradle下载慢问题
- React Native 编译命令
- React Native源码编译
- ZhiHuDaily-React-Native编译
- Android旧项目集成React Native简易流程
- 运行React Native到Android真机上的流程
- React-Native在android原生上的绘制流程
- react native编译需要android ndk版本问题
- react native 新建/打开项目 gradle 卡住
- 4年技术经验
- Android手把手教你实现滑动隐藏(GeastureDetector使用)
- 2016.11.05【初中部 NOIP提高组 】模拟赛C题解
- 进程表项 文件表项 V节点总结
- javascriptde面向对象
- React Native Android Gradle 编译流程浅析
- Understand the probabilistic way of doing SLAM
- 图的拓扑排序
- leetcode (22) - Generate Parentheses
- 数据结构-QS-应用-停车场
- oracle建表语句
- iOS10:CallKit的黑名单以及标示号码功能实践
- Android: requires android.permission.READ_EXTERNAL_STORAGE, or grantUriPermission()
- Dirac函数