Gradle 基础的认知和学习套路分享

写在前面


自从用上了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 有整体的感性认识,便于自行研读原汁原味的官方文档。

全文分主要分两部分:

  1. Gradle 基础知识的理解
    • 以本人理解后的用语,尽量通俗。
    • 不求内容全面,但求向读者介绍主要内容,对阅读理解官方文档的内容有所帮助。
  2. 在学习、使用 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 图的规范,请见谅。

Gradle 基础的认知和学习套路分享_第1张图片
Gradle相关API类图.jpg

类图主要展示了 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、以及脚本文件在一个生命周期中的情况。


Gradle 基础的认知和学习套路分享_第2张图片
Gradle运行流程图.jpg

前面提到 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 生命周期」作为切入点,把各个零散的知识点串联起来,把疑问逐个解破,也是一种求知的技巧。

你可能感兴趣的:(Gradle 基础的认知和学习套路分享)