前言
Gradle是Android构建系统的重点,需要花费时间用心学习。学习资料主要是官方的Gradle Docs,有个社区版的中文翻译资料,英语不好的同学可以看看,但不全。
这章首先对Gradle做一个简单的介绍,然后讲如何使用官方文档和API,接着讲解Gradle的基本概念、Wrapper和学习中需要使用到的命令行指令、插件等。有些知识点如插件应该放在之后的章节讲解,但为了保持文章的完整性,我放在了这一章,学习的过程中可能不能完全理解,没有关系,在以后的章节中如果有涉及的话,会具体解释。
Gradle简介
Gradle是一个注重灵活性和性能的开源构建自动化工具,使用Groovy或Kotlin DSL来编写构建脚本。
虽然支持Kotlin,但还是建议学习Groovy。因为:一,目前官方示例里有许多还没有kotlin方式;二,目前大多数项目的Gradle脚本都是用Groovy编写的。
Gradle特点如下:
- 高度可定制。Gradle使用了可定制和可扩展的设计思想,如使用插件。
- 快速。Gradle通过重用先前执行的输出,仅处理已更改的输入以及并行执行任务来快速完成任务。
- 强大。Gradle是Android的官方构建工具,并支持许多主流的编程语言和技术。
图形界面和命令行 Gradle支持IDE或命令行两种方式来构建工程。支持主流的IDE,如Android Studio, Eclipse, IntelliJ IDEA, Visual Studio 2017, 和XCode等。
提示:命令行方式构建是必须掌握的,因为命令行方式包含所有的功能,而图形界面方式却未必。在调试时,命令行方式尤其重要。
典型的工程目录 一个典型Gradle项目一般会包括两个脚本文件(settings.gradle
和build.gradle
)和Wrapper文件(gradle文件目录和gradlew、gradlew.bat可执行文件)。如下所示
1 | ├── settings.gradle |
其中settings.gradle
和build.gradle
是项目构建时所需的脚本,Gradle会据此创建相应的Java实例。Wrapper文件用于管理gradle
版本,一个终端上不同的项目可以使用不同配置和版本的Gradle,具体项目中使用./gradlew
或gradlew.bat
来替代gradle
命令。
Gradle Docs的使用
Gradle Docs资料中,最重要的是User Manual(用户手册)和API文档(DSL Reference和Javadoc),其余的可以不用看。
Gradle User Manual User Manual中最关键的一部分当属“Build Configuration Scripts”(官网经常换名字,可能已经不叫这个标题了)。这一部分内容相当多,内容也比较杂。可以先看Build Lifecycle和Configuring Multi-Project Build这两节,看懂这两节,就可以对Gradle构建的流程有一个清晰的了解了,之后再看其他章节就非常容易了。
API文档 API文档包括Gradle DSL Reference(Gradle特定语言指南) 和 Javadoc(Java文档)。Reference包含主要的接口和类,并做了描述和分类以利于学习,Javadoc包含所有的API,主要用于检索。就内容的覆盖面而言,Javadoc完全包含Reference。学习时可以看Reference,使用中具体查找Gradle的某个API文档时,用Javadoc。
基础概念
使用Gradle构建项目时,会经常涉及到几个重要的概念:脚本(Script)、项目(Project)和任务(Task)。这几个概念出现的顺序如下:
- 开发者编写脚本,如
settings.gradle
和build.gradle
; - 构建项目时,Gradle会根据构建脚本创建对应的Java实例,如
settings.gradle
对应Setting,build.gradle
对应Project,其中Project
包含构建需要的任务集。然后,Gradle根据这些实例依次执行初始化、项目配置和任务执行等来构建项目。
脚本(Script)
利用Gradle构建项目时,与开发者直接交互的就是脚本了。Gradle脚本有以下特点:
- 每个脚本都有一个对应的Java实例,称为代理对象,在脚本中可以直接使用代理对象的属性和方法。
- Gradle脚本实现了
Script
接口。此接口定义了许多属性和方法,可以在脚本中直接使用。 - Gradle脚本由零或多个语句和脚本块组成(statements and script blocks)。 语句包括方法调用、属性赋值和局部变量定义等。 脚本块是一种方法调用,它以闭包作为方法的参数,而闭包用来配置代理对象。
项目(Project)
项目是Gradle的最基本的概念之一。
Gradle通过build.gradle
脚本生成实现了Project
接口的项目实例,每个Gradle构建都由一个或多个项目组成。Project
接口是脚本文件与Gradle交互的主要API,可以通过Project
实例访问Gradle的所有功能。
任务(Task)
项目由一个或多个任务组成。 每个任务执行一些基本工作,例如编译类、运行单元测试或压缩WAR文件等。
任务由一系列Action对象组成。 执行任务时,通过调用Action.execute(T)
依次执行每个Action
。 可以通过调用Task.doFirst(org.gradle.api.Action)
或Task.doLast(org.gradle.api.Action)
向任务添加Action
。
概念之间的关系 Gradle中项目和任务、Action的关系如下:
1 | graph TD |
浏览官方文档 以上概念详细说明和使用,请见官方手册和指南:
- 脚本。Writing Build Scripts、Script Reference
- 项目。Project Reference
- 任务。Build Script Basics、Authoring Tasks、Writing Custom Task Classes、Task Reference
以上文档可以不用深读,但需要浏览一遍,知道大概。
Gradle Wrapper
前言中已经提到了Wrapper,即一种管理当前项目的Gradle版本的工具。多人协作时,每个人安装的Gradle环境可能不一致(也没有必要一致),需要使用Wrapper管理项目的版本。官方强烈推荐使用Wrapper管理具体的项目,并以./gradlew
(macOS)代替gradle
执行命令。
Wrapper目录中有一个gradle-wrapper.properties
文件,配置Gradle的版本号、本地存储地址等。
引入Wrapper 使用命令行添加Wrapper有两种方式:
- 使用
gradle init
创建新项目,则会初始化一个带有Wrapper的Gradle项目。 - 使用
gradle wrapper
在旧的项目中添加Wrapper。wrapper
是Gradle的内建任务
一般的IDE创建项目时都会自动生产Wrapper文件,如Android Studio。
使用Wrapper执行任务 用Wrapper脚本替换掉gradle
来执行任务即可。以macOS平台为例,在项目根目录下执行./gradlew [task name]
即可,如列出当前项目的所有任务,项目根目录下执行:
1 | ./gradlew tasks |
更新Wrapper 有两种方式更新Wrapper
- 命令行方法。
.gradlew wrapper --gradle-version [要更新的版本号]
。 - 修改
gradle/wrapper/gradle-wrapper.properties
中的distributionUrl
属性。
关于Gradle Wrapper的详细说明请见官方手册The Gradle Wrapper
命令行
指令形式 Gradle命令行指令由三部分组成: 可执行文件gradle
或./gradlew
、一个或多个任务名、零或多个选项名,如下所示:。
1 | gradle [taskName...] [--option-name...] |
指令的详细说明请见官方手册Command-Line Interface,这里我只介绍学习或调试中常用的指令。部分常见命令行选项说明如下:
固有任务类
gradle build
。生成所有的输出,并执行所有的检查。gradle clean
。删除build文件目录。gradle projects
。查看项目结构。gradle tasks
。查看任务列表。查看某个任务详细信息,可用gradle help --task someTask
gradle dependencies
。查看依赖列表。
调试类
-?
,-h
,--help
。查看帮助信息。-v
,--version
。查看版本信息。-s
,--stacktrace
。执行任务时,打印栈信息。如gradle build -s
日志类
-q
,--quiet
。只打印errors类信息。-i
,--info
。打印详细的信息。
其中在学习的过程中,经常会用到
1 | $ gradle [taskName...] -i |
这会打印比较详细的构建信息,用于帮助我们理解Gradle构建流程、项目配置和依赖、任务依赖等。
环境配置
配置构建环境,主要配置Gradle构建参数和对应的JVM参数,如代理策略等。其目的是为了多人协作时,保持在一致的环境下进行项目开发。
配置环境有几种途径,优先级从高往低,列出如下:
- 命令行。
GRADLE_USER_HOME
目录中的gradle.properties
文件。- 项目根目录中的
gradle.properties
文件。 - 环境变量。运行Gradle环境的变量,如JAVA_HOME等。
具体参数的配置请见官方手册Build Environment
依赖管理
现在的项目基本上都需要使用第三方库,一般称其为依赖,这样说并不准确,官方的定义是:
A dependency is a pointer to another piece of software required to build, test or run a module
依赖是指向软件模块的指针,而非模块本身
依赖管理是一种以自动方式声明、解析和使用项目所需的依赖的技术。
依赖管理流程
- 声明。在构建脚本中声明依赖和对应的仓库。
- 解析。项目构建时,Gradle会定位依赖的本地路径或远程服务器路径并下载依赖(有必须要的话)。
- 缓存。依赖解析后,Gradle会将从远程服务下载的依赖缓存到本地,避免下次构建时重复下载。
依赖的类型
- 模块依赖(Module dependencies)。指向一个库中的模块,这是最常见的依赖类型。
- 文件依赖。指向无需仓库的一系列文件。
- 项目依赖。指向多项目工程主项目需要用到的其他项目。
- 特定分发的依赖。如API依赖、TestKit依赖和本地Groovy依赖等。
常见的仓库
- 文件系统仓库。通过查找文件目录以解析依赖。
- Maven Central 仓库。一般默认的Maven仓库。
- JCenter Maven 仓库。Android默认的Maven仓库。
- Google Maven 仓库。Google的支持库所在的Maven仓库。
- 本地 Maven仓库。就是你开发项目所用的主机中的仓库。
- 自定义Maven仓库。一些公司,如阿里,喜欢把阿里云OSS服务SDK等存储在自己搭建的Maven仓库中。
- Ivy仓库。不常用。
Maven仓库是用Apache Maven工具搭建的存储不同类型资源的本地或远程资源服务。Maven Central仓库、JCenter Maven仓库和Google Maven仓库都是Maven仓库,区别在于存储的服务器和管理的机构不一样。
插件(Plugin)
插件简介 Gradle的一个非常大的特点就是灵活或者说可扩展性,它本身没有提供多少构建具体任务的逻辑,构建各类具体的项目都是通过增加插件实现的。插件可以添加新任务(例如JavaCompile),新的域对象(例如SourceSet),新的规定(例如Java源位于src/main/java)以及继承其他插件而获得的功能。
Gradle本身提供了一些基础的插件,只需要配置id即可使用,如 Java Plugin
。
二进制插件及配置 插件分为两种,本地脚本插件和远程发布的二进制插件,一般我们常用的是后者。如Android项目中我们用到的Android插件,就是一种远程二进制插件。
Android项目添加插件示例:
在根目录build.gradle
中添加仓库和插件路径
1 | buildscript { |
在app目录build.gradle
中应用插件
1 | apply plugin: 'com.android.application' |
插件的使用 Gradle使用插件里的构建逻辑分为两步
- 解析插件。即获取插件的jar包。插件被解析后,就可以在脚本文件中直接使用它的API,如Android项目脚本文件中的配置
android
、方法compileSdkVersion
等。本地脚本插件和Gradle固有插件会自动解析。 - 应用插件。即执行插件的
Plugin.apply(T)
方法,这是插件的核心方法。在这个方法中,可以为当前项目添加一些新的任务。
关于插件更详细的说明请见官方手册:
如何你想查看Gradle源码,看看插件是如何实现的,链接如下:
示例-单项目构建
官方示例 Creating New Gradle Builds
主要练习:初始化项目、执行任务、移动文件、添加Plugin等。
提示:示例中使用了
./gradlew
来执行命令,如果因为下载失败导致该命令不可用,使用gradle
命令即可。
示例-多项目构建
官方示例 Creating Multi-project Builds
提示:示例中需要下载依赖,国内网络直接访问可能导致下载失败,需要设置国外代理服务。我的代理服务走的通道是
shadowsocks
,有的资源使用shadowsocks
协议是访问不了的,需要使用http
或https
协议才行,可以使用privoxy
切换shadowsocks
协议为http
和https
协议(实际上是在本地shadowsocks
端口上再加了一层privoxy
代理)。shadowsocks
代理设置请参考Github shadowsocksprivoxy
代理设置请参考 Privoxy 官网、使用Privoxy桥接Http代理到SOCKS5代理、Privoxy 教程
Gradle项目代理设置如下:在电脑中设置好http
协议代理后,在当前项目根目录下创建gradle.properties
文件(如果有就不用创建),编辑文件如下:
1
2
3
4
5
6 systemProp.http.proxyHost=127.0.0.1
systemProp.http.nonProxyHosts=192.168.*, <localhost>
systemProp.http.proxyPort=8118
systemProp.https.proxyHost=127.0.0.1
systemProp.https.nonProxyHosts=192.168.*, <localhost>
systemProp.https.proxyPort=8118其中
8118
是privoxy
的端口号
Gradle代理设置详细说明请参考官方文档Accessing the web via a proxy(虽然也不够详细:p)
示例-Android项目根目录 build.gradle
语法分析
利用Android Studio
创建一个项目,根目录中build.gradle
文件如下
1 | buildscript { |
直接使用API文档来分析上面的代码:
Project
和方法。build.gradle
脚本的代理对象是Project
实例,buildscript
、allprojects
和task
是Project
中的三个方法(实际上,task
在这里是关键字,不是方法,但Project
中有方法与之对应)。buildscript
。在Android Studio
中(Mac
平台Command+鼠标左键
)或者在文档中查看API详情,如下

这个方法的作用是配置项目构建脚本的classpath
,参数是一个闭包,闭包的代理对象是ScriptHandler
,即如果buildscript
代码块中的属性和方法,如果不能在Project
中找到,就去ScriptHandler
中去找。
再来分析下buildscript
代码块里的内容。示例代码中,repositories
和dependencies
都是ScriptHandler
的方法,可以直接在Android Studio
中点击查看其API。但ext
是什么,是谁的属性?owner
(Project
)的,还是delegate
(ScriptHandler
)的?由于不能在Studio
里直接查看,我们先看看Project
的API
全局搜索ext
或setExt
、getExt
,都没有结果。再看看其继承的接口,发现ExtensionAware中可以搜索到,API文档说
1
2
3
4
5 // All extension aware objects have a special “ext” extension of type >ExtraPropertiesExtension
assert project.hasProperty("myProperty") == false
project.ext.myProperty = "myValue"
// Properties added to the “ext” extension are promoted to the owning >object
assert project.myProperty == "myValue"继承了
ExtensionAware
的对象都有一个特殊的ext
扩展类型,可以直接添加属性project.ext.myProperty = "myValue"
,之后使用属性myProperty
时可以不用写ext
,如project.myProperty
。
因此示例代码中的ext
是owner
(Project
)的属性。
allprojects
。分析方法同上,略。task
。Project
中有四个task
方法
Task task(String name)
Task task(String name, Closure configureClosure)
Task task(Map<String, ?> args, String name)
Task task(Map<String, ?> args, String name, Closure configureClosure)
Studio
中链接的是Task task(String name)
,但后面的clean(type: Delete) { delete rootProject.buildDir }
是什么呢?开始以为是方法,实际上并不是,一是在Project
中没有clean
方法,二是Groovy语法中不允许嵌套的方法省略括号。
Parentheses are required for method calls without parameters or ambiguous method calls:
1 println(Math.max(5, 10))不是方法,但看起来又不是参数。查看
Task
API,有定义task的示例
You can also use the task keyword in your build file:
1
2
3
4 task myTask
task myTask { configure closure }
task myTask(type: SomeType)
task myTask(type: SomeType) { configure closure }也就是说
task
可以是一个关键字!去看Gradle官方文档Defining tasks
There are a few variations on this style, which you may need to use in certain situations. For example, the keyword style does not work in expressions.
有很多方式定义task,有关键字格式:
1 | task copy(type: Copy) { |
我们的示例就是这种格式的。
也有方法格式的:
1 | task('copy', type: Copy) { |
这对应Project
的方法Task task(Map<String, ?> args, String name, Closure configureClosure)
。Groovy方法中的参数似乎是可以改变顺序的,有人写的博客中提到了这件事,但我没有找到相关的官方文档。
终于分析完了,初学者学习这些估计要抓狂,:p。
最后,把示例代码中省略的括号和owner
、delegate
补全,以提高可读性,如下
1 | buildscript({ |
注意: 其中的
classpath
,我暂时并没有理解清楚它是如何工作的,它不是方法,只能理解它是一个可以添加的配置属性。
1 Dependency add(String configurationName, Object >dependencyNotation);