Gradle的一些命令记录:
- 执行一个指定的任务:
gradle -q taskName
// 如:gradle -q hello - 查看所有任务
gradle -q tasks
- 执行多个任务
gradle hello hello2
- 排除任务的执行 -x:
gradle hello -x hello2
// 排除hello2执行
org.gradle.api.Project 项目
在Gradle中,一个项目(project)表示一个正在构建的组件(如:一个jar文件,android中,一个aar文件);
Gradle的build.gradle
文件相当于Maven的pom.xml,每个build.gradle文件在构建启动后,对应于org.gradle.api.Project
的一个实例,并且能够通过project
变量使其隐式可用;
一个Project可用创建新的task
,添加依赖与配置,实际上这些都是 project
接口的方法实现的,如下dependencies
就是调用 Project的 dependencies 方法,并传递闭包;
dependencies {
provided 'com.android.support:support-annotations:25.3.1'
provided 'com.android.support:support-v4:25.3.1'
}
构建Project实例中,可通过代码访问Gradle的所有特性,如:task创建、依赖管理等;当访问属性与方法时,不需要使用project
变量;
setDescription("myProject") // 没有显示指定 project变量
println "Description of project $name: " + project.description; // 访问name与description属性;
在Android开发中,每个module都表示一个Project,有自己独立的 build.gradle脚本;
org.gradle.api.Task 任务
任务task对应的是 org.gradle.api.Task
接口;任务的一些重要功能有:任务动作(task action)和任务依赖(task dependency);
任务动作:定义了一个当任务执行时最小的工作单元;
任务依赖:有些时候,某个任务的运行之前需要其他Task先运行;
属性
每个Project
和Task
实例都提供可通过 getter与setter方法访问的属性,如:项目名,任务名等;
通常,我们需要自定义一些自己的属性;比如:Android的编译版本,minSdk等等;这就用到扩展属性;
扩展属性
在内部,这些属性以键值对的形式存储,添加属性,使用 ext
命名空间,在定义扩展属性的时候,需要使用到 ext
命名空间前缀,如下 :
// 此扩展属性信息,定义在一个单独的gradle文件中,使用的使用
// 在项目的根build.gralde文件中 apply from: "文件名"来引用;
// gradle配置信息
ext {
COMPILE_SDK_VERSION = 25 // 25 23
BUILD_TOOLS_VERSION = "25.0.2" // 25.0.2 23.0.2
MIN_SDK_VERSION = 14
TARGET_SDK_VERSION = 23 // 这个千万不能改
// support依赖支持包
compile_support = [
design : "com.android.support:design:25.3.1",
annotations : "com.android.support:support-annotations:25.3.1",
appcompat : "com.android.support:appcompat-v7:25.3.1",
recyclerview: "com.android.support:recyclerview-v7:25.3.1",
]
// 常用库包
compile_common = [
gson: "com.google.code.gson:gson:2.7"
]
}
使用的时候,可省略ext
前缀,如下:
android {
compileSdkVersion COMPILE_SDK_VERSION
buildToolsVersion BUILD_TOOLS_VERSION
....
Gradle属性
Gradle属性可通过gradle.properties
文件中声明直接添加到项目中,此文件位于 <USER_HOME>/.gradle.properties
目录 或 项目的根目录下,这些属性可以通过项目实例访问;
如果一个project有多个module,也只能有一份 gradle.properties文件;
比如,在properties文件声明了(最好在本项目的文件做实验,不动全局的,见下解释):
myname=zhaoyubetter
通过直接引用名字来访问
println(myname) // 来进行访问;
注意:在android studio 中,android视图中,显示的gradle.properties文件是全局的,与操作系统的是同一个文件,如下:
项目中的 gradle.properties
文件需要切换到 project files 视图,才能看到,如下:
使用Task
默认情况下,每个新创建的Task都是org.gradle.api.DefaultTask
类型的;
声明task动作
动作(action)就是在task中合适的地方放置构建逻辑,Task接口提供2个相关的方法来声明task动作:doFirst(Closure)
(可使用 >>)与 doLast(Closure)
(可使用<<左移);
def curVersion = '1.0.0-SNAPSHOT'
task printVersion {
doLast {
println ">>>>>>>>>>>>>>>>>>verson:${curVersion}"
}
}
// or 写成
task printVersion << {
println ">>>>>>>>>>>>>>>>>>verson:${curVersion}"
}
通过 gradle printVersion 可执行此任务;
访问DefaultTask属性
- description属性:描述任务的作用;
- group属性: 用于定义task的逻辑分组;
创建Task时,可传递,作为参数:
def version = '1.0.0-SNAPSHOT'
task printVersion(group: 'versioning', description: 'Print project version') {
doFirst {
println ">>>>> before reading the version"
}
doLast {
println ">>>>> version: $version"
}
}
// 或者
task printVersion() {
group = 'versioning'
description = 'Print project version'
定义Task依赖
dependsOn
方法,允许声明依赖一个或多个task,通过task依赖关系图来建模完整的task生命周期;
def group = 'myGroup'
task first << { println ">>>> first" }
task second << { println ">>>> second" }
// 依赖多个任务
task printVersion(group: group, dependsOn: [second, first]) << {
println ">>>> the version : $version"
}
task third << { println ">>> third" }
third.dependsOn('printVersion') // 声明依赖时,按名称引用task
输入:gradle -q third
, 输出如下:
task 依赖的执行顺序
Gradle不能保证依赖的执行顺序,dependsOn只是定义了所依赖的task需要先执行;在gradle中,执行顺序是由task的输入/输出规范自动确定的;
终结器Task
使用 task 的finalizedBy
来使用一个特定的终结器:
task first << { println ">>>> first" }
task second << { println ">>>> second" }
first.finalizedBy second // 执行 gradle -q first 时,会触发 task second
添加任意代码,设置版本信息
gradle就是groovy,所以可以加入一些groovy代码,如下:
// 版本
class ProjectVersion {
int major
int minor
int min
boolean release
ProjectVersion(int major, int minor, int min) {
this.major = major
this.minor = minor
this.min = min
this.release = false
}
ProjectVersion(int major, int minor, int min, boolean release) {
this.major = major
this.minor = minor
this.min = min
this.release = release
}
@Override
public String toString() {
return "$major.$minor.$min${release ? '' : '-SNAPSHOT'}"
}
}
def version = new ProjectVersion(1, 0, 0)
task printVersion << {
println(" >>>>>> the verson is ${version}")
}
添加task配置块
task配置块会在task动作之前被执行;
- 新建配置文件
version.properties
如下:
major=1
minor=0
min=0
release=false
- 添加
Gradle配置
loadVersion(没有<<
或>>
运算符) ,并读取 properties文件
ext.versionFile = file('version.properties')
// loadVersion任务,没有 << or >> 运算符,Gradle称之为 task配置
task loadVersion {
project.version = readVersion()
}
ProjectVersion readVersion() {
println(">>>>> reading the version file")
if(!versionFile.exists()) {
throw new RuntimeException(">>> Required version file does not exist: ${versionFile.canonicalPath}")
}
Properties prop = new Properties()
versionFile.withInputStream { stream ->
prop.load(stream)
}
new ProjectVersion(prop.major.toInteger(), prop.minor.toInteger(), prop.min.toInteger(), prop.release.toBoolean())
}
task printVersion << {
println(" >>>>>> the verson is ${version}") // 访问project的属性version
}
Gradle构建生命周期阶段
执行gradle构建,会运行三个不同的生命周期阶段:初始化、配置和执行;
如下图:
- 初始化阶段:gradle为项目创建一个Project实例;
- 配置阶段:Gradle构造一个模型来表示任务,此阶段用来为项目或指定的task设置所需的配置;项目的每一次构建的任何配置代码都可以被执行,即使只执行 gradle tasks;
- 执行阶段:执行task动作;执行的顺序由他们的依赖决定;如果任务被认为没有修改过,将跳过;
声明task的inputs和outputs
Gradle通过比较2个构建task的inputs和outputs来决定是否是最新的;当 inputs和outputs不同时,task才运行,否则跳过;
输入可以是一个目录,文件或属性等,一个task的输出是通过一个目录或1~n个文件来定义的;
inputs与outputs在DefaultTask
类中,被定义为属性或者直接子类来表示;
创建Task用来发布产品版本,并自动修改配置文件
// 产品发布版本
task makeReleaseVersion(group: 'versioning', description: 'Makes project a reelase version.') << {
version.release = true
// ant task 的propertyfile 提供便利的方式修改属性文件 (version.properties)
ant.propertyfile(file: versionFile) {
entry(key: 'release', type: 'string', operation: '=', value: 'true')
}
}
通过运行 :gralde makeReleaseVersion
可以发现配置文件,确实修改了;
但Gradle并不知道 我们的 是发布版本,为了解决,需要声明它的inputs与outputs
task makeReleaseVersion(group: 'versioning', description: 'Makes project a reelase version.') {
// 在配置阶段声明 inputs/outputs
inputs.property('release', version.release) // 声明版本的release属性作为输入
outputs.file versionFile // 由于版本文件被修改,所以他被声明最为输出文件属性
doLast { // 闭包为动作代码
version.release = true
ant.propertyfile(file: versionFile) {
entry(key: 'release', type: 'string', operation: '=', value: 'true')
}
println(">>>>>> makeReleaseVersion")
}
}
task的inputs和outputs是在配置阶段执行的用来连接task依赖,需要在配置块中被定义,而且需要确保赋给 inputs和outputs的值在配置阶段是可访问的;
如果需要编程获得输出,可通过TaskOutputs上的upToDateWhen(Closure)
方法实现,此方法是在执行阶段执行的;如果闭包返回true,则认为该task时最新的;
如果2次执行 gradle makeReleaseVersion
后一次gradle知道项目版本被设置为发布版本了,并自动跳过第二次执行;
使用自定义Task
自定义Task包含2个组件:自定义的task类(封装逻辑行为)与真实的task(提供用于配置行为的task类所暴露的属性值);gradle称之为增强型task;
可维护性是编写自定义task类的优势之一,毕竟操作的是一个实际的类;
编写自定义Task
继承自 DefaultTask
,使用注解表示输入输出;
class ReleaseVersionTask extends DefaultTask {
@Input Boolean release // 注解声明输入属性 release
@OutputFile File destFile // 定义输出文件
ReleaseVersionTask() {
group = 'versioning'
description = 'Makes project a realase version.'
}
// 使用注解声明将被执行的方法 ,
// 加上这个action的作用是当执行这个task的时候会自动执行这个方法
@TaskAction
void start() {
project.version.release = true
ant.propertyfile(file: versionFile) {
entry(key: 'release', type: 'string', operation: '=', value: 'true')
}
}
}
使用自定义Task
在构建脚本中,创建一个ReleaseVersionTask
类型的task,并且通过它的属性赋值来设置输入和输出,如:
// 增強型task暴露的属性单独赋值
task makeReleaseVersion2(type: ReleaseVersionTask) {
release = version.relase
destFile = versionFile
}
task规则
某些时候,编写的task可能做着相同的事情,如下版本的管理task任务:
// 主版本+1
task incrementMajorVersion(group: 'versioning', description: 'Increments project major version.') << {
String currentVersion = version.toString()
++version.major
String newVersion = version.toString()
logger.info "Incrementing major project version: $currentVersion -> $newVersion"
ant.propertyfile(file: versionFile) {
entry(key: 'major', type: 'int', operation: '+', value: 1)
}
}
// 次版本+1
task incrementMinorVersion(group: 'versioning', description: 'Increments project major version.') << {
String currentVersion = version.toString()
++version.minor
String newVersion = version.toString()
logger.info "Incrementing minor project version: $currentVersion -> $newVersion"
ant.propertyfile(file: versionFile) {
entry(key: 'minor', type: 'int', operation: '+', value: 1)
}
}
分别运行命令:gradle incrementMinorVersion -i
,可以看到输出;
但上面的是否可以改进呢?
task规则的命名模式
根据task名称模式执行特定的逻辑,模式由2部分组成:
task名称的静态部分 与 一个占位符,他们联合起来组成一个命令;
如上面的,像:increment<Classifier>Version
Gralde的一些核心插件,利用了task规则,如:clean<TaskName>;
声明task规则
首先需要获得对TaskContainer
的引用,然后可调用其addRule(String, Closure)
方法,通过project的getTasks()方法获取 TaskContainer引用;
使用task规则,实现上线动作:
// 使用 taskContainer 添加规则
tasks.addRule("Pattern: increment<Classifier>Version - Increments the project version.") {
String taskName ->
if (taskName.startsWith('increment') && taskName.endsWith('Version')) {
task(taskName) << { // 符合命名模式的task动态添加doLast方法
String classifier = (taskName - 'increment' - 'Version'.toLowerCase()) // 提取
String currentVersion = version.toString()
switch (classifier) {
case 'major': ++version.major
break
case 'minor': ++version.minor
break
default: throw new GradleException("Invalid version type '$classifier', Allowed " +
" types: ['Major','Minor']")
}
String newVersion = version.toString()
logger.info "Incrementing $classifier project version: $currentVersion -> $newVersion"
ant.propertyfile(file: versionFile) {
entry(key: classifier, type: 'int', operation: '+', value: 1)
}
}
}
}
通过运行gradle tasks
会列出一个具体的tasks组rules:
task 规则不能分组,其显示在 Rules组下;
我们运行命令:gradle incrementMajorVersion -i
,输出如下:
在buildSrc目录下构建代码
可以将构建脚本的一些groovy类:如上面的ProjectVersion和自定义的task ReleaseVersionTask,这些适合移动到项目的 buildSrc
目录下;
Gradle在buildSrc目录下使源文件结构标准化,Java代码在 src/main/java
,Groovy代码放在src/main/groovy
目录下;这些目录下代码会自动编译,并添加到Gradle构建脚本的classpath中;
Android Studio 中在项目的根目录,建立buildSrc文件夹,并sync一下工程,然后新增文件夹路径 src/main/groovy/包名, 就可以创建groovy脚本了;
在当前 module下对应的 build.gradle文件,直接引入 buildSrc 下的类,这样Task定义与build.gradle分离了;
import com.better.ProjectVersion2
import com.better.ReleaseVersionTask2
总结:
了解一下Task的工作细节、自定义Task,也了解一些Task配置与Task动作之间的区别;最后我们在buildSrc中,实现自定义Task的分离,将task独立出来;