写在前面
自从用上了Android Studio 进行 Android 开发,不可避免地要与 Gradle 打交道。它颠覆了过去使用 Eclipse + ADT 时代(暴露年龄...)对 Android 构建认知,惊喜于构建效率的改进,同时因对 Gradle 本身的不了解,或者说不知道应该了解啥,导致面对构建出错见红,往往都不知所措、无从下手。
直观印象中:
Gradle 进行构建的效率高、使用方便,对照文档调整参数,修改脚本就可以了。
Gradle 也不容易理解,Android Studio 官方只说了插件怎么用,有哪些能力,但知识点多琐碎,体系脉络稍欠清晰,单纯看着脚本语法例子也不好理解记忆。
正文前言
AS(Android Studio)、AGP(Android Gradle Plugin)、
AP DSL(Android Plugin DSl),是日常研发不离手的东西,它们之间通过Gradle,建立起紧密的联系。
网络上也有不少文章,对 Gradle 构建脚本进行详细讲解,例如:Gradle基础脚本全攻略,也值得细细研读。
写这篇文章的目的,是想分享自己在学习(啃Gradle官方文档)Gradle 过程中,提炼的认知和整理,既方便日后的知识快速回溯,同时也希望能帮助大家对 Gradle 有整体的感性认识,便于自行研读原汁原味的官方文档。
全文分主要分两部分:
- Gradle 基础知识的理解
- 以本人理解后的用语,尽量通俗。
- 不求内容全面,但求向读者介绍主要内容,对阅读理解官方文档的内容有所帮助。
- 在学习、使用 Gradle 过程中,解决问题的一些套路和思维。
Gradle基础知识
我参考阅读的文档版本为 Gradle Docs V4.1。正如前面所说,跟人感觉官方文档的内容编排是单纯地从使用角度,循序渐进地来展开。如果把这个文档看成一个人,她应该在说:
首先,你要安装下载安装 Gradle,基本的使用方式、命令行、Wapper 很好很强大强推你用、多项目怎么构建、Bla Bla Bla ...
非常棒,现在你对 Gradle 有了**感性认识**。然后,要介绍怎样写构建脚本,这是清单列表:
- Build Script 的基本概念
- 项目和任务、脚本怎样初始化
- 聊聊任务、怎样处理文件、你可以直接用 Ant
- 构建其实有生命周期的
- 日志功能我们也有
- 依赖管理、多项目构建;
- 其实我们还有 A插件、B插件、C插件、Bla Bla Bla插件...
你想自定义构建?当然可以,重要事情说多遍:插件、插件、插件 ...
私以为,把 Wrapper 编排在靠前位置,对知识体系初期理解和建立,不太很友好。倒不如从“生命周期”开始说起,会比较好理解。
1.Gradle运行的生命周期
Initialization(初始化) | Configuration(配置) | Execution(执行) |
---|---|---|
初始化配置、系统版本、环境变量版本检查、Gradle 版本检查,Gradle 类路径载入 | 执行相关 .gradle 脚本,确定要执行的项目和任务,并建立可执行的不可循环有向图 | 根据有向图的顺序,执行任务 |
1.1 Initialization(初始化)阶段
这个阶段有 90% 的工作跟 Gradle 进程启动有关:
- 检查运行的机器上对应的依赖库、版本、Gradle SDK 路径
和版本等 Gradle 运行环境的检查工作- 加载 Gradle SDK、启动 Daemon 进程,正式开始干活
另外的10%,是在启动的 Gradle 运行环境中,创建项目对象,
- 寻找 settings.build 脚本文件,解析所有合法的目录,寻找 build.gradle 脚本文件
- 创建对应 build.gradle 脚本文件的Project对象
1.2 Configuration(配置)阶段
各个 Project 对象都已经初始化完毕(虽然文档用词是 configured,个人认为用“初始化”比较贴切),这个阶段开始执行对应的构建脚本 build.gradle 中脚本代码。
- 执行
allprojects{}
、subprojects{}
等脚本块的配置设置- 创建项目的所有任务 Task 对象,分析建立本次构建要执行的任务有向图。
简单概括:配置 Project、创建 Tasks、创建 Tasks 有向图 DAG。
1.3 Execution(执行)阶段
根据有向图的任务依赖顺序,执行 Tasks。
看似简单地一句话带过,但实际上在整个 Gradle 运行中,干体力活最多的节点。
关于“生命周期”的概念,相信看官们都容易理解,官方文档也写的清晰,下面在“生命周期”层往深处进一步看看。
2.脚本内容及代理对象的关系映射的理解
以下是一个常见的项目构建脚本 build.gradle 文件。
// 配置
buildscript {
repositories {...} // 定义构建脚本依赖的库仓库
dependencies {...} // 定义构建脚本的依赖
}
allprojects {
// 闭包隐含参数it,相当于(当前project + subprojects)集合的for循环体
// 每个project都有,只是在rootProject用得比较多
}
subprojects {
// 闭包隐含参数it,相当于subprojects集合的for循环体
//每个project都有,只是在rootProject用得较多
}
构建脚本中代码的作用,在Gradle构建语言参考中都能找到明确的说明,但不够详尽。实际上,还有很多 DSL 文档没有列出的API可供使用,需要我们自行去发掘。
首先,列出在 Gradle API 中重要的类:
Project、Task、Action、(Groovy)Closure、ScriptHandler、NamedDomainObjectContainer、RepositoryHandler、ArtifactHandler、DependencyHandler、PluginManager
这些类都是脚本文件中,常见的代理对象类型。关于代理对象、代理设计模式等概念认知,就不在这里阐述。
下图所示为 Gradle 相关 API 类图。为求直观理解,并没有严格遵守 UML 图的规范,请见谅。
类图主要展示了 Project 相关的三部分内容:
1.依赖配置相关的内容
2.插件的能力
3.暴露 DSL 相关的依赖类
build.gradle 构建脚本代码片段1:
buildscript {
repositories {...}
dependencies {...}
}
按照官方文档的描述,build.gradle 对应一个 Project 对象,通过方法 getBuildscript()
获取 ScriptHandler
对象。
/**
* Returns the build script handler for this project. You can use this handler to query details about the build
* script for this project, and manage the classpath used to compile and execute the project's build script.
*
* @return the classpath handler. Never returns null.
*/
ScriptHandler getBuildscript();
/**
* Configures the build script classpath for this project.
*
*
The given closure is executed against this project's {@link ScriptHandler}. The {@link ScriptHandler} is
* passed to the closure as the closure's delegate.
*
* @param configureClosure the closure to use to configure the build script classpath.
*/
void buildscript(Closure configureClosure);
ScriptHandler
是脚本块 buildscript{}
的代理对象类型。
DSL中的配置语句,实际被解析为对 ScriptHandler
代理对象的相关属性set
方法,该动作是在“配置阶段”执行。至于 DSL 代码是如何被解析的,就不在这篇文章里深入了。
build.gradle 构建脚本代码片段2:
repositories {...}
dependencies {...}
同理,Project 类还有 getRepositores()
和 getDependencies()
方法
/**
* Returns a handler to create repositories which are used for retrieving dependencies and uploading artifacts
* produced by the project.
*
* @return the repository handler. Never returns null.
*/
RepositoryHandler getRepositories();
/**
* Configures the repositories for this project.
*
*
This method executes the given closure against the {@link RepositoryHandler} for this project. The {@link
* RepositoryHandler} is passed to the closure as the closure's delegate.
*
* @param configureClosure the closure to use to configure the repositories.
*/
void repositories(Closure configureClosure);
/**
* Returns the dependency handler of this project. The returned dependency handler instance can be used for adding
* new dependencies. For accessing already declared dependencies, the configurations can be used.
*
*
Examples:
* See docs for {@link DependencyHandler}
*
* @return the dependency handler. Never returns null.
* @see #getConfigurations()
*/
DependencyHandler getDependencies();
/**
* Configures the dependencies for this project.
*
*
This method executes the given closure against the {@link DependencyHandler} for this project. The {@link
* DependencyHandler} is passed to the closure as the closure's delegate.
*
*
Examples:
* See docs for {@link DependencyHandler}
*
* @param configureClosure the closure to use to configure the dependencies.
*/
void dependencies(Closure configureClosure);
build.gradle 构建脚本代码片段 3:
subprojects {
repositories {...}
dependencies {...}
}
对应 API:
/**
* Returns the set containing the subprojects of this project.
*
* @return The set of projects. Returns an empty set if this project has no subprojects.
*/
Set getSubprojects();
/**
* Configures the sub-projects of this project
*
* This method executes the given {@link Action} against the sub-projects of this project.
*
* @param action The action to execute.
*/
void subprojects(Action action);
/**
* Configures the sub-projects of this project.
*
* This method executes the given closure against each of the sub-projects of this project. The target {@link
* Project} is passed to the closure as the closure's delegate.
*
* @param configureClosure The closure to execute.
*/
void subprojects(Closure configureClosure);
通过以上三个脚本代码片段及对应的 Gradle API,我们可以理解为:
在 build.gradle 脚本文件中,可以用面向对象的思维去看待脚本代码,并使用。作为开发者,用面向对象的思维去理解东西,应该是比较熟悉的。
在「Gradle生命周期」之中,有两个重要的抽象:项目 Project 和任务 Task。
下图所示为简单的 Gradle 运行流程图。重点感受一下 Project、Task、以及脚本文件在一个生命周期中的情况。
前面提到 DSL 的配置代码是在“配置阶段”执行,那么怎样才能在“执行阶段”执行呢?看看以下的例子:
task clean(type: Delete) {
doFirst {
println 'do first'
}
delete rootProject.buildDir
println 'do config'
}
task test(type:Delete, dependsOn:clean) {
doLast {
println 'do last'
}
}
执行命令,调用任务test "./gradlew test",输出运行结果:
> Configure project :
do config
> Task :clean UP-TO-DATE
do first
> Task :test
do last
BUILD SUCCESSFUL in 1s
2 actionable tasks: 1 executed, 1 up-to-date
因为“执行阶段”只会执行 Task 的内容,所以前面的 DSL 配置内容不会在此阶段被运行。
doFirst 和 doLast 是 Task 的 DSL 语法。常用的插件如: java、com.android.application 等,都是通过Plugin机制,在初始化项目的时候创建了所有的Task对象,能满足大部分的工作需要。
当遇到需要自定义 Task 的情况,如果工作内容不复杂,可以直接在构建脚本中定义。但如果比较复杂,可采取建立 buildSrc 目录模块或者自定义插件类实现,这里就不深入展开了。
学习经验小结
1.较快且有效的阅读文档方法
- 重视对目录的咀嚼:用思维导图把目录摘录一遍,最好能在脑海里能列出重要部分章节标题。
- 精读并建立自己的索引(逐字逐句翻译琢磨)一遍,不求记住所有,只需要有印象即可。同时,加上“自联想短语”建立一种知识体系的记忆索引,有助于后面的回溯。例如:Gradle 是平台、能力源自插件、项目和任务都是对象、项目对象映射文件夹等等
2.快速查找、确定相关插件 DSL(文档未提及的)可用的属性、参数配置的步骤:
- 首先,在 Groovy DSL Reference 文档 ,定位脚本块的代理对象,进而确定对象的类。
- 然后,在 Gradle API Javadoc 文档 ,查找该类,进一步查看相关属性或方法。
3.尝试把 build.gradle 脚本文件的内容,对号入座地套进「Gradle 生命周期」执行流程中理解。
结语
要完全掌握 Gradle,不是三言两语能详述,对于刚接触 DSL 概念的同学看官更是抽象不易懂。通过「Gradle 生命周期」作为切入点,把各个零散的知识点串联起来,把疑问逐个解破,也是一种求知的技巧。