2024年Android最新补齐Android技能树 - 玩转Gradle(二) _ 小册免费学(1),程序员面试

面试复习笔记:

这份资料我从春招开始,就会将各博客、论坛。网站上等优质的Android开发中高级面试题收集起来,然后全网寻找最优的解答方案。每一道面试题都是百分百的大厂面经真题+最优解答。包知识脉络 + 诸多细节。
节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

《960页Android开发笔记》

《1307页Android开发面试宝典》

包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。

《507页Android开发相关源码解析》

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

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

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

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

上图中有三处提到了 Script,在Gradle中,他们是 配置脚本,脚本在执行时,实际上是配置了一个特殊类型的对象:

Init Script → Gradle对象、Settings Script → Settings对象、Build Script → Project对象;

这个对象又称脚本的 代理对象,代理对象上的每个属性、方法都可以在脚本中使用。

每个Gradle脚本都实现了Script接口,由0个或多个 脚本语句 (statements)和 脚本块 组成。 脚本语句可以包含:函数调用、属性赋值和本地变量定义,脚本块则是一个方法调用,传入一个 配置闭包,执行时对代理对象进行配置。

本节就来细说下 构建生命周期依赖规则&依赖冲突处理,这部分内容比较枯燥,但我依旧会写得简洁易懂些,希望看完对你解决Android多模块构建问题时可以有所裨益。

0x1、Initialization(初始化)

1. Init Script(初始化脚本)

涉及到的文件及脚本执行顺序如下:

$GRADLE_USER_HOME/init.gradle(.kts)$GRADLE_USER_HOME/init.d/[*.gradle(.kts)]

这一步会生成 Gradle 对象,提供了三类API:获取全局属性、项目配置、生命周期HOOK,部分API如下:

// 获得Gradle实例的方法:在*.gradle文件中调用.gradle 或 Project.getGradle()。

/* ========= ① 获取全局属性 ========= */

gradleHomeDir → 执行此次构建的Gradle目录;
gradleUserHomeDir → Gradle User Home目录;
gradleVersion → 当前Gradle版本;
includedBuilds → 获取内嵌构建;
parent → 获取父构建;
pluginManager → 获取插件管理器实例;
plugins → 获取插件容器;
rootProject → 获取当前构建的根项目;
startParameter → 获取传入当前构建的所有参数
taskGraph → 获取当前构建的task graph,此对象在taskGraph.whenReady { } 后才具有内容

/* ========= ② 项目配置,闭包方法会在Project可用时立即执行 ========= */

rootProject(action) // 为Root Project添加闭包
allprojects(action) // 为所有 Project添加闭包

应用示例:Gradle全局设置Maven仓库,创建一个 $GRADLE_USER_HOME/init.gradle(.kts) 或在 $GRADLE_USER_HOME/init.d/ 目录下随便创建一个xxx.gradle(.ktx)文件,内容如下:

// 项目依赖仓库
allprojects {
repositories {
maven { url “https://maven.aliyun.com/repository/google” }
maven { url “https://maven.aliyun.com/repository/jcenter” }
}
}

// Gradle脚本依赖仓库
gradle.projectsLoaded {
rootProject.buildscript {
repositories {
maven { url “https://maven.aliyun.com/repository/google” }
maven { url “https://maven.aliyun.com/repository/jcenter” }
}
}
}

配置后,Gradle项目构建时会优先从这里的Maven仓库下载依赖,然后再到项目中配置的仓库中下载,在Gradle编译提速中,可把Maven地址替换为自己搭建的Maven私服,所以Gradle项目编译时都会优先走这里~


2. Settings Script(设置脚本)

涉及文件:项目根目录下的 settings.gradle(.kts),在此文件中:声明参数构建的模块管理构建过程需要的插件,此处会生成一个 Settings 对象。

Gradle会从当前目录开始查找此文件,找到停止找不到则往父目录递归查找,所以建议不管是单项目还是多项目,都要有一个 settings.gradle(.kts) 文件。

声明参数构建的模块

Settings类中,最重要的方法就是 include(String… projectPaths) 方法,用于添加参与构建的Project,传入一个 可变参数,值是每个Project的路径( 当前project相对于根project的路径 ),示例如下:

// [:]代表项目分隔符,类似于路径分隔中的[/],以:开头表示相对于根目录
include ‘:module1’
include ‘:libs:library1’
// 也可写到一行
include ‘:module1’,‘:libs:library1’

// 注:当子项目不在根目录下时需使用相对路径描述
project(“:module3”).projectDir = File(rootDir, “…/…/library2”)

// 默认情况下Gradle会使用根项目所在目录名称作为项目名
// 配合CI一起使用时,往往会检测到一个随机文件名,可以强制指定项目名称
rootProject.name = ‘JustTest’

每个被include的项目都会生成 ProjectDescriptor 对象, 用于描述该模块。模块名称最终都会添加到Map类型的 DefaultProjectRegistry.projects 中,所以无需特殊处理include的顺序。

另外,即便settings.gradle(.kts)什么都不写,也会加载当前目录下的Build Script。

管理构建过程需要的插件

通过 settings.pluginManagement 的相关接口实现,比如指定插件的仓库地址(默认从Gradle官方创建仓库查找),打开settings.gradle:

pluginManagement {
// 对应PluginManagementSpec类
repositories { // 管理Plugin Repository(仓库)
google { url “https://maven.aliyun.com/repository/gradle-plugin” }
}
}

rootProject.name = ‘temp’
include ‘:module1’,‘:module2’

利用 resolutionStrategy 接口则可进行插件决策,比如打印一个Kotlin项目用到的插件信息:

resolutionStrategy {
eachPlugin { // 接收一个PluginResolveDetails类型的闭包,通过requestsd可以获得plugin的信息
println “${requested.id} → ${requested.module} → ${requested.version}”
}
}

输出结果如下:

接着可以根据id替换插件或指定插件版本,示例如下:

resolutionStrategy {
eachPlugin { // 接收一个PluginResolveDetails类型的闭包,requested可以获得plugin的信息
println “${requested.id} → ${requested.module} → KaTeX parse error: Expected '}', got 'EOF' at end of input: ….gradle.plugin:{requested.version}”)
}
// 统一插件版本
if (requested.id.id == “org.jetbrains.kotlin.jvm”) {
useVersion(“1.3.71”)
}
}
}

另外,此阶段涉及到的两个生命周期事件:settingsEvaluated() 和 projectLoaded(),前者可以拿到配置完毕的 Setting 对象,后者可以拿到包含项目基础信息的 Project 对象。


3. Build Script(构建脚本)

涉及文件:模块目录下的 build.gradle(.kts),用于配置当前模块的 构建信息,分为:

  • 根目录模块的 Root Build Script (一般是对子模块进行统一的配置,没有太多内容);
  • 子模块的 Module Build Script

多模块的构建流程:Init ScriptSettings ScriptRoot Build Script(单模块没这一步) → Build Script (默认字母序,可通过设置依赖关系干预)

Build Script完成的工作有两个:插件引入属性配置,即对 Project 对象进行进一步的配置,生成Task的有向无环图。

插件引入

Gradle自身 并没有提供编译打包的功能,它只是一个 负责定义流程和规则的框架,具体的编译工作都是由 插件 来完成的,比如编译Java用Java插件,编译Kotlin用Kotlin插件。

所以插件到底是什么?→ 答:定义Task,并具体执行这些Task的模板

插件的两种类型:

  • 脚本插件:存在于另一个脚本文件中的一段脚本代码;
  • 二进制插件(编译成字节码):实现Plugin接口,通过编程的方式操作构建过程(项目或Jar包形式);

Gradle会内置一些核心插件,并提供简单名字,如 “java”,没在其中的插件则需采用完整名字,如:“org.jetbrains.kotlin:kotlin-gradle-plugin”,这个又称插件id,唯一不可重复!引入方式区别如下:

// 内置插件引入
apply plugin: ‘kotlin-android’
// 也可以使用plugins,不过有些插件不能指定版本,有些必须指定,要注意!
// 下面这种写法是Kotlin中的中缀表达式,apply→ 是否立即应用插件
plugins {
id(“org.jetbrains.kotlin.jvm”) version “1.3.71”
id(“org.jetbrains.kotlin.jvm”) version “1.3.71” apply false
java
build-scan
}

// 非内置插件引入,会将对应Jar文件放到Gradle的classpath下
buildscript {
repositories {
jcenter()
}
dependencies {
classpath “org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72”
}
}

属性配置

一旦应用了某个插件,就可以使用此插件提供的DSL进行配置,以此干预模块的构建过程。以Android构建为例:

// 引入android.application插件 → 为Project对象添加一个android{}配置闭包
apply plugin: ‘com.android.application’

android {
compileSdkVersion 29 // 使用API 29编译此模块

// 编译时的一些配置
defaultConfig {
applicationId “com.example.test”
minSdkVersion 26
targetSdkVersion 29
versionCode 1
versionName “1.0”
}

// 签名配置
signingConfigs {
release {
storeFile file(‘test.jks’)
storePassword ‘123456’
keyAlias ‘test’
keyPassword ‘123456’
}
}

// 构建类型配置
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile(‘proguard-android-optimize.txt’), ‘proguard-rules.pro’
signingConfig signingConfigs.release
}
}

// 编译选项配置
compileOptions{
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}

除了插件另外引入的属性DSL外,Project对象也提供了很多用于配置构建的DSL,如 dependencies 配置编译依赖项,更多可以点进Project源码中自行查看。

另外根目录Build Script还可以使用一个 ext 属性用于Project间的数据共享、统一模块依赖版本。

// 根目录build.gradle配置
ext {
applicationId = “xxx.xxx.xxx”
buildToolsVersion = “28.0.3”
compileSdkVersion = 28
minSdkVersion = 22

}

// 子模块build.gradle使用
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion

}


0x2、依赖规则

Gradle会声明每个依赖项的适用范围,可以理解为 分组,如:有些依赖在编译时用到,有些则在运行时用到,Gradle通过 Configuration 来表示这个范围(分组),不同的Configuration通过不同的name区分。

许多Gradle插件会预置一些Configuration添加到你的项目中,如Java插件:

有点懵,没关系,看下Android内置的这些Configuration你就懂了(2.x是废弃的):

// 对应2.x的 compile,既参与编译又参与打包
implementation → 当前模块依赖,但不向其他模块暴露此依赖,编译时只能在本模块访问;
api → 当前模块依赖,且向其他模块暴露此依赖,同compile;

举例区分下:
模块A、B,如果A依赖了Gson库,B依赖A,使用implementation,B用不了Gson,使用api,B可以使用Gson。

// 对应2.x的provided和apk,用的较少
compileOnly → 编译时有效,不会参与打包;
runtimeOnly → 运行时有效,不会参与编译;

annotationProcessor → 注解处理器依赖

testCompile → 对应2.x的testImplementation,只在单元测试代码的编译以及最终打包测试apk时有效;
debugCompile → 对应2.x的debugImplementation,只在debug模式的编译和最终的debug apk打包时有效;
releaseCompile → releaseImplementation,只在release的编译和最终的release apk打包时有效;

附:四种依赖方式

// ① 本地library依赖
implementation project(“:mylibrary”)

// ② 本地二进制依赖
implementation files(‘libs/xxx.jar’, ‘libs/yyy.jar’) // 依赖特定库
implementation fileTree(dir: ‘libs’, include: [‘*.jar’]) // 依赖目录下的库

// ③ 远程二进制依赖
implementation(‘io.reactivex:rxandroid:1.2.1’)

// ④ AAR包依赖
implementation(name: ‘me.leolin:ShortcutBadger’, ext: ‘aar’) // 本地
implementation ‘me.leolin:ShortcutBadger:1.1.17@aar’ // 远程

当然,你也可以自定义一个Configuration,示例如下:

allprojects {
// 配置maven仓库地址
repositories {
maven { url “https://maven.aliyun.com/repository/jcenter” }
}
}

// 定义一个名为myDependency的Configuration
configurations {
myDependency
}

// 为自定义Configuration添加依赖
dependencies {
myDependency(‘io.reactivex:rxjava:1.1.9’)
myDependency(‘io.reactivex:rxandroid:1.2.1’)
}

// 打印自定义Configuration下载依赖后的文件地址
task showMyDependency {
println configurations.myDependency.asPath
}

终端键入:gradle showMyDependency,输出结果如下:

还可以调 extendsFrom 方法来继承另一个Configuration的所有dependencies,比如implementation就继承了compile。

最后还得提提两个标志,默认都为true:

  • canBeResolved:编译时依赖
  • canBeConsumed:运行时依赖

0x3、依赖创建的过程

Tips:跟源码了解下原理,不感兴趣可以直接跳过,不影响后续学习~

1. 依赖识别

build.gradle 处点开 dependencies,定位到了 Project 类:

看注释:传递的闭包由此Project的 DependencyHandler 执行,定位到此接口:

可以看到每个方法都返回 Dependency 实例,点开这个接口:

噢吼,接口定义了获取group、name、version的方法,回到 DependencyHandleradd() 方法,最多可传递三个参数:

implementation(io.reactivex:rxandroid:1.2.1) {
transitive = false
}

Dependency add(String configurationName, Object dependencyNotation, Closure configureClosure);

// 参数一一对应:

implementation、io.reactivex:rxandroid:1.2.1、后面跟着的大括号(依赖配置闭包)

AS只能跟到这里,接着用VS Code打开Gradle的源码,全局搜下 implements DependencyHandler,定位到 DefaultDependencyHandler 类:

该类除了实现 DependencyHandler 接口外,还实现了一个**MethodMixIn** 接口:

在讲解这个接口前,我们先来了解下Groovy语言的两个特性:invokeMethodmethodMissing,先介绍下前者:

package groovy.reflect

class InvokeTest1 {
def hello() {
‘执行Hello方法’
}

def invokeMethod(String name, Object args) {
return “未知方法 n a m e ( name( name({args.join(‘,’)})”
}

static main(args) {
def it = new InvokeTest1()
println it.hello()
println it.foo(“test”, 28)
}
}

// 运行输出:
// 执行Hello方法
// 未知方法 foo(test, 28)

对于一个对象的方法调用,类中有此方法就分发给此方法,如果不能分派,就调用invokeMethod方法,而methodMissing同样能实现上面的效果:

最后

我的面试经验分享可能不会去罗列太多的具体题目,因为我依然认为面试经验中最宝贵的不是那一个个具体的题目或者具体的答案,而是结束面试时,那一刻你的感受以及多天之后你的回味~

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

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

【Android核心高级技术PDF文档,BAT大厂面试真题解析】

【算法合集】

【延伸Android必备知识点】

【Android部分高级架构视频学习资源】

**Android精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

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

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

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

【Android部分高级架构视频学习资源】

**Android精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值