引言
Gradle是Android构建系统的重点,需要花费时间用心学习。学习资料主要是官方的Gradle Docs。
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在构建时,会根据构建脚本创建对应的Java实例,如settings.gradle
对应Setting接口,build.gradle
对应Project接口。
There is a one-to-one correspondence between a Settings instance and a settings.gradle settings file. Before Gradle assembles the projects for a build, it creates a Settings instance and executes the settings file against it.
There is a one-to-one relationship between aProject
and abuild.gradle
file.
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构建的流程有一个清晰的了解了,之后再看其他章节就非常容易了。
Gradle User Manual
API文档
API文档包括Gradle DSL Reference(Gradle特定语言指南) 和 Javadoc(Java文档)。Reference包含主要的接口和类,并做了描述和分类以利于学习,Javadoc包含所有的API,主要用于检索。就内容的覆盖面而言,Javadoc完全包含Reference。学习时可以看Reference,使用中具体查找Gradle的某个API文档时,用Javadoc。
几个基础概念
脚本(Script)
利用Gradle构建项目时,与开发者直接交互的就是脚本了。Gradle脚本有以下特点:
- 每个脚本都有一个对应的代理对象。Gradle脚本是一种配置脚本,在脚本执行时,Gradle会配置特定类型对象,称为脚本的代理对象。脚本与代理对象一一对应,并且在脚本中可以直接使用代理对象的属性和方法。各Gradle脚本对应的代理对象如下:
Type of script | Delegates to instance of |
---|---|
Build script | Project |
Init script | Gradle |
Settings script | Settings |
- Gradle脚本实现了
Script
接口。此接口定义了许多属性和方法,可以在脚本中直接使用。 - Gradle脚本由零或多个语句和脚本块组成(statements and script blocks)。 语句包括方法调用、属性赋值和局部变量定义等。 脚本块是一种方法调用,它以闭包作为方法的参数,而闭包用来配置代理对象。
项目(Project)
项目和任务是Gradle中的两个最基本的概念,所有的内容都基于它们。
每个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
。
以上概念详情,可查看它们的Reference。
示例-单项目构建
官方示例 Creating New Gradle Builds
主要练习:初始化项目、执行任务、移动文件、添加Plugin等。
提示:示例中使用了
./gradlew
来执行命令,如果因为下载失败导致该命令不可用,使用gradle
命令即可。
示例-多项目构建
官方示例 Creating Multi-project Builds
提示:示例中需要下载依赖,国内网络直接访问可能导致下载失败,需要设置国外代理服务。
Gradle项目代理设置如下:在电脑中设置好http
协议代理后,在当前项目根目录下创建gradle.properties
文件(如果有就不用创建),编辑文件如下: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
文件如下
buildscript {
ext.kotlin_version = '1.2.50'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.4'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
直接使用API文档来分析上面的代码:
-
Project
和方法。build.gradle
脚本的代理对象是Project
实例,buildscript
、allprojects
和task
是Project
中的三个方法(实际上,task
在这里是关键字,不是方法,但Project
中有方法与之对应)。 -
buildscript
。在Android Studio
中(Mac
平台Command+鼠标左键
)或者在文档中查看API详情,如下
buildscript
这个方法的作用是配置项目构建脚本的classpath
,参数是一个闭包,闭包的代理对象是ScriptHandler
,即如果buildscript
代码块中的属性和方法,如果不能在Project
中找到,就去ScriptHandler
中去找。
再来分析下buildscript
代码块里的内容。示例代码中,repositories
和dependencies
都是ScriptHandler
的方法,可以直接在Android Studio
中点击查看其API。但ext
是什么,是谁的属性?owner
(Project
)的,还是delegate
(ScriptHandler
)的?由于不能在Studio
里直接查看,我们先看看Project
的API
Project
全局搜索ext
或setExt
、getExt
,都没有结果。再看看其继承的接口,发现ExtensionAware中可以搜索到,API文档说
// 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:
println(Math.max(5, 10))
不是方法,但看起来又不是参数。查看Task
API,有定义task的示例
You can also use the task keyword in your build file:
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,有关键字格式:
task copy(type: Copy) {
from(file('srcDir'))
into(buildDir)
}
我们的示例就是这种格式的。
也有方法格式的:
task('copy', type: Copy) {
from(file('srcDir'))
into(buildDir)
}
这对应Project
的方法Task task(Map<String, ?> args, String name, Closure configureClosure)
。Groovy方法中的参数似乎是可以改变顺序的,有人写的博客中提到了这件事,但我没有找到相关的官方文档。
终于分析完了,初学者学习这些估计要抓狂,:p。
最后,把示例代码中省略的括号和owner
、delegate
补全,以提高可读性,如下
buildscript({
owner.ext.kotlin_version = '1.2.50'
delegate.repositories({
delegate.google()
delegate.jcenter()
})
dependencies({
delegate.add('classpath', 'com.android.tools.build:gradle:3.1.4')
delegate.add('classpath', "org.jetbrains.kotlin:kotlin-gradle-plugin:${owner.kotlin_version}")
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
})
})
allprojects({
delegate.repositories({
delegate.google()
delegate.jcenter()
})
})
task clean(type: Delete) {
delete rootProject.buildDir
}
注意: 其中的
classpath
,我暂时并没有理解清楚它是如何工作的,它不是方法,只能理解它是一个可以添加的配置属性。Dependency add(String configurationName, Object >dependencyNotation);
网友评论