Gradle 构建脚本基础

Settings 文件

Gradle 中,定义了一个设置文件,用于初始化以及工程树的配置。设置文件的默认名字是 settings.gradle ,放在根工程目录下。设置文件大多数的作用都是为了配置子工程。在 Gradle 中多工程是通过工程树表示的,就相当于我们在 Android Studio 看到的 ProjectModule 概念一样。根工程相当于 Android Studio 中的 Project,一个根工程可以有很多子工程,也就是很多 Module ,这样就和 Android Studio 定义的 Module 概念对应上了。一个子工程只有在 Settings 文件里配置了 Gradle 才会识别,才会在构建的时候被包含进去。

rootProject.name = 'GradleTest'

include ':example02'
project(':example02').projectDir = new File(rootDir, 'chapter01/example02')

上例这就是我们示例工程的 setting 配置,可以看到我定义了很多子项目,并且为它们指定了相应的目录,如果不指定,默认目录是其同级的目录。比如 include: example02,如果我不指定, Gradle 就会把当前同级的 example02 目录作为 example02 工程的目录,但是没有这个目录,就会报错。利用这个特性,我们可以把我们的工程放到任何目录下,可以非常灵活地对我们的工程进行分级、分类等,只要 Settings 文件里指定好路径就可以了。

Build 文件

每个 Project 都会有一个 Build 文件,该文件是该 Project 构建的入口,可以在这里针对该 Project 进行配置,比如配置版本,需要哪些插件,依赖哪些库等。既然每个 Project 都会有一个 Build 文件,那么 Root project 也不例外。 Root project 可以获取到所有的 Child Project,所以在 Root ProjectBuild 文件里我们可以对 Child Project 统一配置,比如应用的插件,依赖的 Maven 中心库等。

Projects 以及 tasks

Gradle 中,可以有很多 Project ,你可以定义创建一个 Project 用于生成一个 jar,也可以定义另外一个 Project 用于生成一个 war 包,还可以定义一个 Project 用于发布上传你的 war 等。其实一个 Project 就是在你的业务范围内,被你抽象出来的一个个独立的模块,你可以根据项目的情况抽象归类,最后这一个个的 Project 组成了你的整个 gradle 构建。从我们编程的角度讲,它就是一个个独立的模块。好好利用它们,这样你的代码就能够做到低耦合、高内聚。

一个 Project 又包含很多个 Task ,也就是说每个 Project 是由多个 Task 组成的。Task 就是一个操作,一个原子性的操作,比如打个 jar 包,复制一份文件,编译一次 Java 代码,上传一个 jarMaven 中心库等,这就是一个 Task ,和 Ant 里的 TargetMaven 中的 goal 是一样的。

创建一个任务

task customTask {
    doFirst {
        println 'customTask:doFirst'
    }
    doLast {
        println 'customTask:doLast'
    }
}

这里的 Task 看着像一个关键字,其实它是 Project 对象的一个函数,原型为 create(Sting name, Closure configure Closure)customTask 为任务的名字,我们可以自定义;第二个参数是个闭包,也就是我们花括号里的代码块。根据我们前面讲的 Groovy 知识,最后一个参数是闭包的时候,可以放到括号外面,然后方法的括号可以省略,就生成了我们上面的写法,很简洁。该闭包的作用就是用来对我们创建的任务进行配置,例子中我们用了任务的 dofirstdolast 方法,分别在任务执行前后输出一段文字。

除了上面的方法,我们还可以通过 TaskContainer 创建任务。在 Gradle 中,Project 对象已经帮我们定义好了一个 TaskContainer ,就是 tasks

tasks.create ('customTask2'){
    doFirst {
        println 'customTask:doFirst'
    }
    doLast {
        println 'customTask:doLast'
    }
}

这种创建任务的方法跟上一种效果是一样的,只是名字不一样。

任务依赖

任务之间是可以有依赖关系的,这样我们就能控制哪些任务先于哪些任务执行;哪些任务执行后,其他任务才能执行。比如我们运行 jar 任务之前, compile 任务一定要执行过,也就是 jar 依赖于 compileAndroidinstall 任务一定要依赖 package 任务进行打包生成 apk ,然后才能 install 设备里。

task test2 << {
    println 'test2'
}

task test(dependsOn: test2){
    doLast {
        println 'test'
    }
}

在创建任务的时候,通过 dependsOn 可以指定其依赖的任务。

task test2 << {
    println 'test2'
}

task test(dependsOn: test2){
    doLast {
        println 'test'
    }
}


task test3 {
    dependsOn test,test2
    doLast {
        println 'test3'
    }
}

dependsOnTask 类的一个方法,可以接收多个依赖的任务作为参数。同时我们也可以使用 TaskAPI 访问它的方法属性或者对任务重新配置。

task ex36Hello << {
    println 'dowLast1'
}

ex36Hello.doFirst {
    println 'dowFirst'
}

ex36Hello.doLast {
    println project.hasProperty('ex36Hello')
    println 'dowLast2'
}

和变量一样,要使用任务名操纵任务,必须先定义声明,因为脚本是顺序执行的。运行上述代码后:

由结果可知,Project 在创建任务的时候,同时把该任务对应的任务名注册为 Project 的一个属性,类型是 Task

自定义属性

ProjectTask 都允许用户添加额外的自定义属性,要添加额外的属性,通过应用所属对应的 ext 属性即可实现。添加之后可以通过 ext 属性对自定义属性读取和设置,如果要同时添加多个自定义属性,可以通过 ext 代码块:

//自定义一个Project的属性
ext.age = 18

//通过代码块同时自定义多个属性
ext{
    phone = 183207612
    address = 'nanshan'
}

task hello << {
    println "年龄是:${age}"
    println "电话是:${phone}"
    println "地址是:${address}"  
}

在我们的项目中一般使用它来自定义版本号和版本名称,把版本号和版本名称单独放在一个 Gradle 文件中。因为它们每次发布版本都会改变,变动频繁,放到一个单独的 Gradle 文件中,便于管理,而且改动的时候也不会因为 Git 冲突影响整个 Buid 文件,便于解决冲突。

脚本即代码 代码也是脚本

虽然我们在一个 Gradle 文件中写脚本,但是我们写的都是代码,这一点一定要记清楚,这样你才能时刻使用 GroovyJava以及 Gradle 的任何语法和 API 帮你完成你想做的事情。在这脚本文件上你可以定义 Class 内部类、导入包、定义方法、常量、接口、枚举等。
我们在项目中需要给生成的 APK 包以当前日期的格式命名,我们就定义了一个获取日期格式的方法,用于生成 APK 的文件名:

def buildTime(){
    new Date().format('yyyyMMdd')    
}

这只是使用的一个例子,目的是让大家灵活搭配 JavaGroovyGradle,不要把它当成普通的脚本文件。