作为一个 Android 开发程序员, 如果你的 build.gradle 都只能靠 IDE 生成或者从别的项目中复制粘贴来完成, 那么你该好好的看完这篇文章, 掌握一下你不知道的 Gradle 基础.
文中的图片均来自于网络, 侵删
Gradle 是一个基于 JVM 的构建工具, 目前 Android Studio 中建立的工程都是基于 gradle 进行构建的. Gradle 的与其他构建工具 (ant,maven) 的特性主要包括:
强大的 DSL 和丰富的 gradle 的 API
gradle 就是 groovy
强大的依赖管理
可拓展性
与其他构建工具的集成
三种构建脚本
Gradle 的脚本都是配置型脚本. 每一种脚本类型实际上都是某个具体的 gradle 的 API 中的类对象的委托, 脚本执行对应的其实是其委托的对象的配置. 在一个完整的 gradle 的构建体系中, 总共有三种类型的构建脚本, 同时也分别对应着三种委托对象
init.gradle
对应的就是上面的 Init script, 实际上就是 Gradle 对象的委托, 所以在这个 init 脚本中调用的任何属性引用以及方法, 都会委托给这个 Gradle 实例.
Init script 的执行发生在构建开始之前, 也是整个构建最早的一步.
配置 Init scrip 的依赖
每个脚本的执行都可以配置当前脚本本身执行所需要的依赖项. Init scrip 的配置如下:
- // initscript 配置块包含的内容就是指当前脚本本身的执行所需要的配置
- // 我们可以在其中配置比如依赖路径等等
- initscript {
- repositories {
- mavenCentral()
- }
- dependencies {
- classpath group: 'org.apache.commons', name: 'commons-math', version: '2.0'
- }
- }
使用 Init scrip
要使用一个定义好的 Init scrip, 主要有以下几个方式
在执行 gradle 命令的时候, 通过 - I 或 --init-script 命令选项指定脚本的路径
这种方式可以针对具体的一次构建.
把一个 init.gradle 文件放到
*USER_HOME*/.gradle/
目录
把一个文件名以. gradle 结尾的文件放到 Gradle 分发包
*GRADLE_HOME*/init.d/
目录内
以上的两种方式是全局的, 对机器内的构建都会起作用
settings.gradle
对应的是 Settings script 脚本类型, 是 Settings 对象的委托. 在 脚本中调用的任何属性引用以及方法, 都会委托给这个 Settings 实例.
Settings script 的执行发生在 gradle 的构建生命周期中的初始化阶段. Settings 脚本文件中声明了构建所需要的配置, 并用以实例化项目的层次结构. 在执行 settings 脚本并初始化 Settings 对象实例的时候, 会自动的构建一个根项目对象 rootProject 并参与到整个构建当中.(rootProject 默认的名称就是其文件夹的名称, 其路径就是包含 setting 脚本文件的路径).
下面是一张关于 Settings 对象的类图:
每一个通过 include 方法被添加进构建过程的 project 对象, 都会在 settings 脚本中创造一个 ProjectDescriptor 的对象实例.
因此, 在 settings 的脚本文件中, 我们可以访问使用的对象包括:
Settings 对象
Gradle 对象
ProjectDescriptor 对象
获取 settings 文件
在 gradle 中, 只要根项目 / 任何子项目的目录中包含有构件文件, 那么就可以在相应的位置运行构建. 而判断一个构建是否是多项目的构建, 则是通过寻找 settings 脚本文件, 因为它指示了子项目是否包含在多项目的构建中.
查找 settings 文件的步骤如下:
在与当前目录同层次的 master 目录中搜索 setting 文件
如果在 1 中没有找到 settings 文件, 则从当前目录开始在父目录中查找 settings 文件.
当找到 settings 文件并且文件定义中包含了当前目录, 则当前目录就会被认为是多项目的构建中的一部分.
build.gradle
对应的就是前面提到的 Build script 脚本类型, 是 gradle 中 Project 对象的委托. 在脚本中调用的任何属性引用以及方法, 都会委托给这个 Project 实例.
配置脚本依赖
在 build.gradle 文件中有一个配置块 buildScipt{}是用于配置当前脚本执行所需的路径配置等的(与 initScript 形似).
- buildscript {
- // 这里的 repositories 配置块要与 Project 实例当中的 repositories 区分开来
- // 这里的 repositories 配置是指脚本本身依赖的仓库源, 其委托的对象实际上是 ScriptHandler
- repositories {
- mavenLocal()
- google()
- jcenter()
- }
- // 与前面的 repositories 配置块相同, 也要与 Project 当中的 dependencies 配置块区分开来
- dependencies {
- classpath 'com.android.tools.build:gradle:3.1.2'
- }
- }
三个构建块
每个 gradle 构建都包含三个基本的构件块:
- project
- task
- property
每个构建包含至少一个 project, 进而又包含一个或者多个 task.project 和 task 暴露的属性 (property) 可以用来控制构建.
Project
我们对 project 的理解更多来源于项目目录中的 build.gradle 文件(因为它其实就是 project 对象的委托).Project 对象的类图如下所示:
项目配置
在 build.gradle 脚本文件中, 我们不仅可以对单独 project 进行配置, 也可以定义 project 块的共有逻辑等, 参考下面的定义.
常见的例子比如:
- // 为所有项目添加仓库源配置
- allprojects {
- repositories {
- jcenter()
- google()
- }
- }
- // 为所有子项目添加 mavenPublish 的配置块
- subprojects {
- mavenPublish {
- groupId = maven.config.groupId
- releaseRepo = maven.config.releaseRepo
- snapshotRepo = maven.config.snapshotRepo
- }
- }
- Task
任务是 gradle 构建的基础配置块之一, gradle 的构建的执行就是 task 的执行. 下面是 task 的类图.
task 的配置和动作
当我们定一个一个 task 的时候, 会包含配置和动作两部分的内容. 比如下面的代码示例:
- task test{
- println("这是配置")
- doFirst{
- // do something here
- }
- doLast(){
- // do something here
- }
- }
目前 task 的动作 (action) 声明主要包含两个方法:
doFirst
doLast
这些动作是在 gradle 的构建生命周期中的执行阶段被调用. 值得注意的是, 一个 task 可以声明多个 doFirst 和 doLast 动作. 也可以为一些已有的插件中定义的 task 添加动作. 比如:
- // 为 test 任务添加一个 doLast 的动作
- test.doLast{
- // do something here
- }
在 task 的定义之中, 除了动作块以外的是配置块, 我们可以声明变量, 访问属性, 调用方法等等. 这些配置块的内容发生在 gradle 的构建生命周期中的配置阶段. 因此 task 中的配置每次都会被执行.(动作块只有在实际发生 task 的调用的时候才会执行).
task 的依赖
gradle 中任务的执行顺序是不确定的. 通过 task 之间的依赖关系, gradle 能够确保所依赖的 task 会被当前的 task 先执行. 使用 task 的 dependsOn()方法, 允许我们为 task 声明一个或者多个 task 依赖.
- task first{
- doLast{
- println("first")
- }
- }
- task second{
- doLast{
- println("second")
- }
- }
- task third{
- doLast{
- println("third")
- }
- }
- task test(dependsOn:[second,first]){
- doLast{
- println("first")
- }
- }
- third.dependsOn(test)
task 的类型
默认情况下, 我们常见的 task 都是
org.gradle.api.DefaultTask
类型. 但是在 gradle 当中有相当丰富的 task 类型我们可以直接使用. 要更改 task 的类型, 我们可以参考下面的示例
- task createDistribution(type:Zip){
- }
更多关于 task 的类型, 可以参考 gradle 的官方文档 https://docs.gradle.org/current/dsl/index.html#N10376
Property
属性是贯穿在 gradle 构建始终的, 用于帮助控制构建逻辑的存在. gradle 中声明属性主要有以下两种方式:
使用 ext 命名空间定义拓展属性
使用 gradle 属性文件 gradle.properties 定义属性
ext 命名空间
Gradle 中很多模型类都提供了特别的属性支持, 比如 Project. 在 gradle 内部, 这些属性会以键值对的形式存储. 使用 ext 命名空间, 我们可以方便的添加属性. 下面的方式都是支持的:
- // 在 project 中添加一个名为 groupId 的属性
- project.ext.groupId="tech.easily"
- // 使用 ext 块添加属性
- ext{
- artifactId='EasyDependency'
- config=[
- key:'value'
- ]
- }
值得注意的是, 只有在声明属性的时候我们需要使用 ext 命名空间, 在使用属性的时候, ext 命名空间是可以省略的.
属性文件
正如我们经常在 Android 项目中看到的, 我们可以在项目的根目录下新建一个 gradle.properties 文件, 并在文件中定义简单的键值对形式的属性. 这些属性能够被项目中的 gradle 脚本所访问. 如下所示:
- # gradle.properties
- # 注意文件的注释是以 #开头的
- groupId=tech.easily
- artifactId=EasyDependency
有的时候, 我们可能需要在代码中动态的创建属性文件并读取文件中的属性(比如自定义插件的时候), 我们可以使用
java.util.Properties
类. 比如:
- void createPropertyFile() {
- def localPropFile = new File(it.projectDir.absolutePath + "/local.properties")
- def defaultProps = new Properties()
- if (!localPropFile.exists()) {
- localPropFile.createNewFile()
- defaultProps.setProperty("debuggable", 'true')
- defaultProps.setProperty("groupId", GROUP)
- defaultProps.setProperty("artifactId", project.name)
- defaultProps.setProperty("versionName", VERSION_NAME)
- defaultProps.store(new FileWriter(localPropFile), "properties auto generated for resolve dependencies")
- } else {
- localPropFile.withInputStream { stream ->
- defaultProps.load(stream)
- }
- }
- }
关于属性很重要的一点是属性是可以继承的. 在一个项目中定义的属性会自动的被其子项目继承, 不管我们是用以上哪种方式添加属性都是适用的.
构建生命周期
前面提及到了 gradle 中多种脚本类型, 并且他们都在不同的生命周期中被执行.
三个阶段
在 gradle 构建中, 构建的生命周期主要包括以下三个阶段:
初始化(Initialization)
如前文所述, 在这个阶段, settings 脚本会被执行, 从而 Gradle 会确认哪些项目会参与构建. 然后为每一个项目创建 Project 对象.
配置(Configuration)
配置 Initialization 阶段创建的 Project 对象, 所有的配置脚本都会被执行.(包括 Project 中定义的 task 的配置块也都会被执行)
执行(Configuration)
这个阶段 Gradle 会确认哪些在 Configuration 阶段创建和配置的 Task 会被执行, 哪些 Task 会被执行取决于 gradle 命令的参数以及当前的目录, 确认之后便会执行
监听生命周期
在 gradle 的构建过程中, gradle 为我们提供了非常丰富的钩子, 帮助我们针对项目的需求定制构建的逻辑, 如下图所示:
要监听这些生命周期, 主要有两种方式:
添加监听器
使用钩子的配置块
关于可用的钩子可以参考 Gradle 和 Project 中的定义, 常用的钩子包括:
- Gradle
- beforeProject()/afterProject()
等同于 Project 中的 beforeEvaluate 和 afterEvaluate
settingsEvaluated()
settings 脚本被执行完毕, Settings 对象配置完毕
projectsLoaded()
所有参与构建的项目都从 settings 中创建完毕
projectsEvaluated()
所有参与构建的项目都已经被评估完
- TaskExecutionGraph
- whenReady()
task 图生成. 所有需要被执行的 task 已经 task 之间的依赖关系都已经确立
- Project
- beforeEvaluate()
- afterEvaluate()
依赖管理
在前面提及的 Gradle 的主要特性之中, 其中的一点就是强大的依赖管理. Gradle 中具备丰富的依赖类型, 兼容多种依赖仓库. 同时 Gradle 中的每一项依赖都是基于特定的范围 (scope) 进行分组管理的.
在 gradle 中添加为项目添加依赖的方式如下所示:
- // build.gradle
- // 添加依赖仓库源
- repositories {
- google()
- mavenCentral()
- }
- // 添加依赖
- // 依赖类型包括: 文件依赖, 项目依赖, 模块依赖
- dependencies {
- // local dependencies.
- implementation fileTree(dir: 'libs', include: ['*.jar'])
- ...
- }
四种依赖类型
Gradle 中的依赖类型有四类:
模块依赖
这是 gradle 中比较常见的依赖类型, 它通常指向仓库中的一个构件, 如下所示:
- dependencies {
- runtime group: 'org.springframework', name: 'spring-core', version: '2.5'
- runtime 'org.springframework:spring-core:2.5',
- 'org.springframework:spring-aop:2.5'
- runtime(
- [group: 'org.springframework', name: 'spring-core', version: '2.5'],
- [group: 'org.springframework', name: 'spring-aop', version: '2.5']
- )
- runtime('org.hibernate:hibernate:3.0.5') {
- transitive = true
- }
runtime group: 'org.hibernate', name: 'hibernate', version: '3.0.5', transitive: true
- runtime(group: 'org.hibernate', name: 'hibernate', version: '3.0.5') {
- transitive = true
- }
- }
模块依赖对应于 gradle 的 API 中的
ExternalModuleDependency
https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/ExternalModuleDependency.html 对象
文件依赖
- dependencies {
- runtime files('libs/a.jar', 'libs/b.jar')
- runtime fileTree(dir: 'libs', include: '*.jar')
- }
项目依赖
- dependencies {
- compile project(':shared')
- }
项目依赖对应于 gradle 的 API 中的 https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/ProjectDependency.html 对象
特定的 Gradle 发行版依赖
- dependencies {
- compile gradleApi()
- testCompile gradleTestKit()
- compile localGroovy()
- }
管理依赖配置
gradle 中项目的每一项依赖都是应用于一个特定的范围的, 在 gradle 中用 https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.Configuration.html 对象表示. 每一个 Configuration 对象都会有一个唯一的名称. Gradle 的依赖配置管理如下所示:
自定义 Configuration
在 gradle 中, 自定义 Configuration 对象是非常简单的, 同时定义自己的 Configuration 对象的时候, 也可以继承于已有的 Configuration 对象, 如下所示:
- configurations {
- jasper
- // 定义继承关系
- smokeTest.extendsFrom testImplementation
- }
- repositories {
- mavenCentral()
- }
- dependencies {
- jasper 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.2'
- }
管理传递性依赖
在实际的项目依赖管理中存在这样的一种依赖关系:
模块 b 依赖于模块 c
模块 a 依赖于模块 b
模块 c 成为了模块 a 的传递依赖
在处理上面这种传递性依赖的时候, gradle 提供了强大的管理功能
使用依赖约束
依赖约束可以帮助我们控制传递性依赖以及自身的依赖的版本号(版本范围), 比如:
- dependencies {
- implementation 'org.apache.httpcomponents:httpclient'
- constraints {
- // 这里 httpclient 是项目本身的依赖
- // 这个约束表示, 不管是项目本身的依赖是还是传递依赖都强制使用这个指定的版本号
- implementation('org.apache.httpcomponents:httpclient:4.5.3') {
- because 'previous versions have a bug impacting this application'
- }
- // commons-codec 并没有被声明为项目本身的依赖
- // 所以仅当 commons-codec 是传递性依赖的时候这段逻辑才会被触发
- implementation('commons-codec:commons-codec:1.11') {
- because 'version 1.9 pulled from httpclient has bugs affecting this application'
- }
- }
- }
排除特定的传递性依赖
有的时候, 我们所依赖的项目 / 模块会引入多个传递性依赖. 而其中部分的传递性依赖我们是不需要的, 这时候可以使用 exclude 排除部分的传递性依赖, 如下所示:
- dependencies {
- implementation('log4j:log4j:1.2.15') {
- exclude group: 'javax.jms', module: 'jms'
- exclude group: 'com.sun.jdmk', module: 'jmxtools'
- exclude group: 'com.sun.jmx', module: 'jmxri'
- }
- }
强制使用指定的依赖版本
Gradle 通过选择依赖关系图中找到的最新版本来解决任何依赖版本冲突. 可是有的时候, 某些项目会需要使用一个较老的版本号作为依赖. 这时候我们可以强制指定某一个版本. 例如:
- dependencies {
- implementation 'org.apache.httpcomponents:httpclient:4.5.4'
- // 假设 commons-codec 的最新版本是 1.10
- implementation('commons-codec:commons-codec:1.9') {
- force = true
- }
- }
要注意的是, 如果依赖项目中使用了新版本才有的 api, 而我们强制使用了旧版本的传递依赖之后, 会引起运行时的错误
禁止传递性依赖
- dependencies {
- implementation('com.google.guava:guava:23.0') {
- transitive = false
- }
- }
在 android plugin 升级到 3.0 以后, 提供了一种新的依赖配置项 implement, 它的作用就是解决了依赖传递性的问题. 模块本身的依赖并不会暴露给被引用的项目
依赖关系解析
使用依赖关系解析规则
依赖关系解析规则提供了一种非常强大的方法来控制依赖关系解析过程, 并可用于实现依赖管理中的各种高级模式. 比如:
统一构件组的版本
很多时候我们依赖一个公司的库会包含多个 module, 这些 module 一般都是统一构建, 打包和发布的, 具备相同的版本号. 这个时候我们可以通过控制依赖关系的解析过程做到版本号统一.
- configurations.all {
- resolutionStrategy.eachDependency { DependencyResolveDetails details ->
- if (details.requested.group == 'org.gradle') {
- details.useVersion '1.4'
- details.because 'API breakage in higher versions'
- }
- }
- }
处理自定义的版本 scheme
- configurations.all {
- resolutionStrategy.eachDependency { DependencyResolveDetails details ->
- if (details.requested.version == 'default') {
- def version = findDefaultVersionInCatalog(details.requested.group, details.requested.name)
- details.useVersion version.version
- details.because version.because
- }
- }
- }
- def findDefaultVersionInCatalog(String group, String name) {
- //some custom logic that resolves the default version into a specific version
- [version: "1.0", because: 'tested by QA']
- }
关于更多依赖关系解析规则的使用实例可以参考 gradle 的 API 中的 https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.ResolutionStrategy.html
使用依赖关系的替代规则
依赖关系的替换规则和上面的依赖关系解析规则有点相似. 实际上, 依赖关系解析规则的许多功能可以通过依赖关系替换规则来实现. 依赖关系的替换规则允许项目依赖 (Project Dependency) 和模块依赖 (Module Dependency) 被指定的替换规则透明地替换.
- // 使用项目依赖替换模块依赖
- configurations.all {
- resolutionStrategy.dependencySubstitution {
- substitute module("org.utils:api") with project(":api") because "we work with the unreleased development version"
- substitute module("org.utils:util:2.5") with project(":util")
- }
- }
- // 使用模块依赖替换项目依赖
- configurations.all {
- resolutionStrategy.dependencySubstitution {
- substitute project(":api") with module("org.utils:api:1.3") because "we use a stable version of utils"
- }
- }
除了上面两种之外, 还有其他三种的依赖关系规则处理. 因为没有实际使用过, 这里不过多阐述, 想了解更多可以查看官方的文档 Customizing Dependency Resolution Behavior https://docs.gradle.org/current/userguide/customizing_dependency_resolution_behavior.html#sec:dependency_substitution_rules
使用组件元数据 (meta-data) 规则
使用组件选择规则
使用模块更换规则
插件开发
插件开发是 gradle 灵活的构建体系中的一个强大工具. 通过 gradle 中的 PluginAPI, 我们可以自定义插件, 把一些通用的构建逻辑插件化并广泛的运用. 比如 Android 项目中都会使用的:
com.android.application
,kotlin-android,java 等等.
网上关于插件开发的文章已经很多, 这里不再赘述. 这里推荐我写的一个 Gradle 插件, 也是在我完全看了 gradle 的官方文档之后, 结合前面提及到的依赖管理的知识写的:
https://github.com/easilycoder/EasyDependency
一个帮助提高组件化开发效率的 gradle 插件, 提供的功能包括:
发布模块的构件都远程 maven 仓库
动态更换依赖配置: 对模块使用源码依赖或者 maven 仓库的构件 (aar/jar) 依赖
写在最后
全文基本是在看了 gradle 的官方文档及相关资料之后, 按照自己的思路的整理和总结. 关于 gradle 的使用和问题欢迎一起讨论.
来源: https://juejin.im/post/5b000522f265da0b7f44d1c7