Gradle 学习记录
学习 gradle dsl 最好莫过于官方文档了: https://docs.gradle.org/current/dsl/index.html 。 学习时可根据 Android 项目进行对应理解。如果之前对 groovy 有了解,特别是闭包,将有助于理解下面的学习。
一、基本概念
在学习 Gradle 脚本之前,有一些基本的概念,了解了这些可以帮助我们更好的编写 Gradle 脚本。
- Gradle脚本是 配置脚本 ,当脚本执行的时候,会对特定类型对象进行 配置。例如,作为一个 build script 执行时,实际上是对一个
Project
类型的对象进行配置。这个被配置的对象 被称为脚本的 delegate object (可按照groovy中closure的delegate理解)。可以在脚本里使用 delegate 对象的任何 属性 和 方法。
脚本类型 | Delegate类型 |
---|---|
Build script | Project |
Settings script | Settings |
- 每一个 Gradle 脚本都 implements
Script
interface(org.gradle.api.Script)。这个 接口 定义了一些列 属性 和 方法,这些都可以被用在脚本里。正如上边看到的,每一个Script
对象都有一个关联的delegate
对象,当在 Script 对象中进行 属性引用 和 方法调用时,如果找不到引用 或者 方法未找到,都会接着去 delegate 对象中寻找。
3、一个 build script 由 语句 和 脚本块 组成。语句 可以包含方法调用、属性赋值、局部变量定义。 脚本块 是一个以闭包为参数的方法调用。当闭包执行时,闭包可以看成一个配置块,对闭包的 delegate 对象进行配置。如下是一些顶级的 脚本块:
Block | Description |
---|---|
allprojects { } |
Configures this project and each of its sub-projects. |
artifacts |
Configures the published artifacts for this project. |
buildscript { } |
Configures the build script classpath for this project. |
dependencies { } |
Configures the dependencies for this project. |
repositories { } |
Configures the repositories for this project. |
sourceSets { } |
Configures the source sets of this project. |
subprojects { } |
Configures the sub-projects of this project. |
.... | .... |
一个 build script 也是一个 Groovy script,所以可以在脚本中使用 Groovy script 允许的元素,比如:方法定义、 class 定义。
二、Gradle 基础使用
1、在gradle构建过程中,有三个阶段:初始化阶段
、配置阶段
、执行阶段
。
2、settings 文件在 初始化阶段 被执行,并且定义了哪些模块应该包含在构建内。 Gradle 会为每个settings文件创建一个 Settings对象,并调用这个对象相关的方法进行配置。
3、gradle 构建生命周期回调:配置阶段执行完毕会回调 afterEvalute
; 执行阶段完成会回调 gradle#buildFinished
4、给 Project 定义扩展属性:
4.1 ext代码块
ext{
compileSdkVersion = 29
buildToolsVersion = "29.0.3"
}
4.2. gradle.properties 文件 (得到的都是String类型,使用时需进行类型转换)
isLoadLibA=true
5、implementation(之前的compile) 传递依赖为false;api 传递依赖为true;compileOnly(之前的provided)占位编译,只参与编译,不打入包,使用场景:
1、类库只处理编译阶段的事情,之后就不会起作用了;
2、工程中已经引入了,无须重新引入了(其实就算引入了也没关系,最后包里只有一份)
三、Project 核心类型
Project
是开发人员 和 Gradle 进行交互的主要 API。通过 Project
可以让我们用 类似编程的思想去使用 Gradle 中各种各样的特性。
3.1 project 常用相关api
Project getRootProject
// 获取根项目
File getRootDir
// 获取本项目的根路径
File getBuildDir
// 获取 本 项目的build路径
Project getParent
// 获取 本 项目的父项目
String getName
// 获取本项目的名字
String getDescription
// 获取本项目的描述
Map<String, Project> getChildProjects
// 获取本项目 的子项目
Set<Project> getAllprojects
// 获取本项目 和 它的子项目
Set<Project> getSubprojects
// 后去本项目的子项目
3.2 提供了 file 方便的api
File file(Object path);
// 通过一个相对于本项目的路径,定位一个文件。
ConfigurableFileCollection files(Object paths, Closure configureClosure);
// 获取文件列表
ConfigurableFileTree fileTree(Object baseDir, Closure configureClosure);
// 根据给定的路径创建一个 FileTree 对象
File mkdir(Object path);
// Creates a directory and returns a file pointing to it.
boolean delete(Object... paths);
// 删除文件和文件夹
ExecResult exec(Closure closure);
//执行一个外部的命令
WorkResult copy(Closure closure);
//Copies the specified files.
接下来放一些例子,看看这些方法怎么使用:
/**访问项目目录下.gitignore文件,并打印出来
*/
println getContent('.gitignore')
//定义方法
def getContent(String path) {
try {
def file = file(path) //可以传入一个相对当前 Project 的项目路径 的 path, 返回一个 File
return file.text
} catch (GradleException e) {
println 'file not find...'
}
return null
}
// 进行文件 拷贝copy. 方便构建,只支持同一个AndroidStudio项目下的便捷方法,更复杂的文件系统处理,还需要使用 文件处理的方式
copy {
from file('app.iml') //从哪
into getRootProject().projectDir.path + "/pic" // 到哪
}
// 文件拷贝 方便构建,只支持同一个AndroidStudio项目下的便捷方法,更复杂的文件系统处理,还需要使用 文件处理的方式
copy {
from file('build/outputs/apk/') // 把生成的 apk
into getRootProject().buildDir.path + "/apk/" // 进行备份
exclude { //配置不想拷贝的进行排除
}
rename { //重命名
}
}
// 对文件树进行遍历(方便构建,只支持同一个AndroidStudio项目下的便捷方法,更复杂的文件系统处理,还需要使用 文件处理的方式)
fileTree('build/outputs/apk') { FileTree fileTree ->
fileTree.visit { FileTreeElement element -> //深度优先
println 'the file name is : ' + element.file.name
copy { CopySpec copySpec ->
/**
* 对闭包熟悉了之后可以去掉,让 build 脚本看起来更简洁,为什么去掉了以后照样可以访问这些闭包参数中的方法呢,
* 因为闭包的 delegate 就是闭包参数类型的具体实现,因为delegate 的方法可直接调用,所以说白了也是调用的
* 闭包参数的方法,只不过利用了 delegate
*/
println delegate //org.gradle.api.internal.file.copy.CopySpecWrapper_Decorated@XXXXXX
copySpec.from element.file
copySpec.into getRootProject().getBuildDir().path + '/test/'
}
}
}
// 创建一个task,并执行shell命令 mv
task("apkcopy") {
doLast {
//gradle执行阶段去执行
def sourcePath = this.buildDir.path + '/outputs/apk'
def desPath = '/Users/jxf/workspace/Android/valuableProject/'
def command = "mv -f ${sourcePath} ${desPath}"
//执行外部命令
exec { // Executes an external command.
try {
executable 'bash'
args '-c', command
println 'the command is execute success'
} catch (GradleException e) {
println 'the command is execute failed'
}
}
}
}
groovy 文件操作在这里也可以使用
四、Task 核心类型
Task是Gradle执行阶段的核心。 所有的Task 按照一定的顺序组成一个 有方向的,无环的图。按照生成的有向无环图,在执行阶段执行这些Task。
4.1 定义Task的几种方式及相关
//直接通过调用 Project#task函数去创建
task helloTask {
/**
* 这些代码都是在 gradle 配置阶段执行的
*/
println 'i am helloTask'
}
//通过TaskContainer去创建Task
// TaskContainer findByPath、create、register、replace 对Task进行管理
this.tasks.create('helloTask2') {
/**
* 这些代码都是在 gradle 配置阶段执行的
*/
group 'jxf'
description 'task study'
println 'i am helloTask2'
}
// 分组可以让Task在控制面板进行归类
task helloTask3(group: 'jack', description: 'task study') { Task task ->
/**
* 这些代码都是在 gradle 配置阶段执行的
*/
println resolveStrategy //OWNER_ONLY == 2
println owner
println 'i am helloTask3'
}
task helloTask4(group: 'jxf', description: "task study") {
//配置阶段执行的代码
println('i aam helloTask4')
/**
* 配置 gradle 执行阶段执行的代码。让Task在Gradle执行阶段去执行的办法是通过 doFrist、doLast 方法,
给Task添加执行的处理逻辑。 Task维护了一个 List<Action<? super Task>> 的列表,当任务执行的,任务的Action会按照顺序依次执行。
*/
doFirst {
Task task
println 'the task group is: ' + group
}
doLast {
}
}
// 计算build执行时长的任务
def startBuildTime, endBuildTime
this.afterEvaluate { Project project -> // gradle 配置阶段完成以后回调 (The project is passed to the closure as a parameter.)
//找到第一个Task
def preBuildTask = project.tasks.getByPath('preBuild')
preBuildTask.doFirst { //挂载到 preBuild 任务
startBuildTime = System.currentTimeMillis()
println 'the start tiem is : ' + startBuildTime
}
//找到 build Task
def buildTask = project.tasks.getByName("build")
buildTask.doLast { //挂载到 build 任务
endBuildTime = System.currentTimeMillis()
println "the build time is: ${endBuildTime - startBuildTime}"
}
}
默认创建的 task 都继承自 DefaultTask。我们也可以使用面向对象的程序开发方式去定义Task及相关配置。
4.2 定义Task的执行顺序:
1、通过dependsOn指定
2、指定输入、输出
3、通过Api指定
task('taskX') { Task task ->
doLast { Task task1 ->
println 'taskX'
}
}
task taskY {
dependsOn(taskX)
// dependsOn:taskY 依赖 taskX。当taskY单独执行时,会先执行taskX,再执行taskY; 也可单独执行taskX,不影响taskY
// mustRunAfter:当两个任务同时执行时,需要才会有排序,否则只有一个任务的时候,不会有影响。这是跟dependsOn是有区别的。
doLast {
println 'taskY'
}
}
//动态添加Task依赖:依赖所有任务是以lib开头的Task
dependsOn this.tasks.findAll {Task task ->
return task.name.startsWith("lib")
}
4.3 Task 常用API
getActions
// 返回顺序task执行的Action 集合
setActions
//设置task执行的action集合
getTaskDependencies
//获取本Task 依赖的Task集合
getDependsOn
//获取本Task 依赖的Task集合
setDependsOn
//设置本Task依赖的任务集合
dependsOn(Object... paths)
//给本Task添加依赖的Task
onlyIf(Closure onlyIfClosure);
//本Task会被执行,仅当闭包返回true。(闭包将在任务执行阶段回调,不是在配置阶段)
doFirst
添加到Action列表的开始位置
doLast
添加到Action列表的末尾位置
boolean getEnabled();
Task是否可用
void setEnabled(boolean enabled);
设置Task是否可用。如果Task不可用,则所有action都不会被执行。但是不会影响此任务 依赖的其它任务的执行。
getInputs
获取Task的输入
getOutputs
获取Task的输出
mustRunAfter
设置此Task必须执行在另外一个Task(或者很多)后面
五、其它
5.1 gradle 版本会一直更新,gradle wrapper 用来确保构建是可重复的。
gradle wrapper 包括三部分:
- gradlew shell脚本
- shell 脚本使用到的 jar 文件
- 一个 properties 文件
5.2 一个修改输出apk文件名称的脚本
import com.android.build.gradle.AppExtension
import com.android.build.gradle.api.ApplicationVariant
import com.android.build.gradle.internal.scope.ApkData
afterEvaluate { Project project ->
def android = project.extensions.findByType(AppExtension.class)
android.applicationVariants.all { ApplicationVariant variant ->
def fileName = "app-${variant.baseName}-${variant.versionName}.apk"
variant.packageApplicationProvider.configure { PackageAndroidArtifact -> packageApplication
packageApplication.outputScope.apkDatas.each { ApkData apkData ->
apkData.outputFileName = fileName
}
}
}
}