2024年安卓最全React Native Android Gradle 编译流程浅析,阿里一面p8

文末

很多人在刚接触这个行业的时候或者是在遇到瓶颈期的时候,总会遇到一些问题,比如学了一段时间感觉没有方向感,不知道该从那里入手去学习,对此我整理了一些资料,需要的可以免费分享给大家

这里笔者分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

【视频教程】

天道酬勤,只要你想,大厂offer并不是遥不可及!希望本篇文章能为你带来帮助,如果有问题,请在评论区留言。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

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"?>

com.facebook.react

react-native

0.33.0

0.33.0

20160909144548

握草!这不就是活生生的 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 = “ f l a v o r N a m e C a p i t a l i z e d {flavorNameCapitalized} flavorNameCapitalized{buildNameCapitalized}”

def targetPath = productFlavorName ?

p r o d u c t F l a v o r N a m e / {productFlavorName}/ productFlavorName/{buildTypeName}” :

“${buildTypeName}”

// React js bundle directories

def jsBundleDirConfigName = “jsBundleDir${targetName}”

def jsBundleDir = elvisFile(config.“$jsBundleDirConfigName”) ?:

file(“ b u i l d D i r / i n t e r m e d i a t e s / a s s e t s / buildDir/intermediates/assets/ buildDir/intermediates/assets/{targetPath}”)

def resourcesDirConfigName = “resourcesDir${targetName}”

def resourcesDir = elvisFile(config.“${resourcesDirConfigName}”) ?:

file(“ b u i l d D i r / i n t e r m e d i a t e s / r e s / m e r g e d / buildDir/intermediates/res/merged/ buildDir/intermediates/res/merged/{targetPath}”)

def jsBundleFile = file(“ j s B u n d l e D i r / jsBundleDir/ 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 t a r g e t N a m e R e s o u r c e s 和 m e r g e {targetName}Resources和merge targetNameResourcesmerge{targetName}Assets之后执行

// Hook bundle p r o d u c t F l a v o r {productFlavor} 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 f l a v o r N a m e C a p i t a l i z e d A r m e a b i − v 7 a {flavorNameCapitalized}Armeabi-v7a flavorNameCapitalizedArmeabiv7a{buildNameCapitalized}Resources”, currentBundleTask)

runBefore(“process f l a v o r N a m e C a p i t a l i z e d X 86 {flavorNameCapitalized}X86 flavorNameCapitalizedX86{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 -> “ j s c A P I B a s e U R L jscAPIBaseURL jscAPIBaseURLheaderName” })

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

如何做好面试突击,规划学习方向?

面试题集可以帮助你查漏补缺,有方向有针对性的学习,为之后进大厂做准备。但是如果你仅仅是看一遍,而不去学习和深究。那么这份面试题对你的帮助会很有限。最终还是要靠资深技术水平说话。

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。建议先制定学习计划,根据学习计划把知识点关联起来,形成一个系统化的知识体系。

学习方向很容易规划,但是如果只通过碎片化的学习,对自己的提升是很慢的。

我搜集整理过这几年字节跳动,以及腾讯,阿里,华为,小米等公司的面试题,把面试的要求和技术点梳理成一份大而全的“ Android架构师”面试 Xmind(实际上比预期多花了不少精力),包含知识脉络 + 分支细节

img

在搭建这些技术框架的时候,还整理了系统的高级进阶教程,会比自己碎片化学习效果强太多。

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

络 + 分支细节**。

img

在搭建这些技术框架的时候,还整理了系统的高级进阶教程,会比自己碎片化学习效果强太多。

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值