文末
很多人在刚接触这个行业的时候或者是在遇到瓶颈期的时候,总会遇到一些问题,比如学了一段时间感觉没有方向感,不知道该从那里入手去学习,对此我整理了一些资料,需要的可以免费分享给大家
这里笔者分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司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 targetNameResources和merge{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 flavorNameCapitalizedArmeabi−v7a{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一个小插曲:
-
由于项目太大、太复杂、太久远,所以之前是 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 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(实际上比预期多花了不少精力),包含知识脉络 + 分支细节。
在搭建这些技术框架的时候,还整理了系统的高级进阶教程,会比自己碎片化学习效果强太多。
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
络 + 分支细节**。
在搭建这些技术框架的时候,还整理了系统的高级进阶教程,会比自己碎片化学习效果强太多。
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!