学向勤中得,萤窗万卷书。这篇文章主要讲述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 版本, 然后编译运行看看。
接着你要是不想在 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一个小插曲:
- 由于项目太大、太复杂、太久远,
所以之前是 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 资源名字诡异坑爹,
自己要多留意下,
我反正当时被坑住了。
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 cleanReactNdkLiblintOptions {
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-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 header
APP_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 切记别直接带出去了。
【React Native Android Gradle 编译流程浅析】那就这样吧, 曲终人散。。。。。明天还有事要处理。。。。。。。
文章图片
【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载, 请尊重作者劳动成果。私信联系我】
推荐阅读
- 实现安卓新闻客户端标题栏的左右切换的问题
- Android 版本对应 Api级别
- Android性能优化典范
- Android Glide数据更新及内存缓存硬盘缓存清理
- 错误(StrictMode $ AndroidBlockGuardPolicy.onNetwork)
- Android开发,海马玩模拟器连接Eclipse
- 解决启动Eclipse后提示’Running android lint’错误的问题
- Android渲染机制和丢帧分析
- Android 图像选取 图片剪裁 照相选图 照相裁剪 大全 6-19更新