最近打算学习下 gradle 在 Android 中的使用,结果百度出来的文章都是介绍性文章,没啥干货。后来找到 gradle 官网教程,本身对着撸。html
Gradle 是一个基于 Apache Ant 和 Apache Maven 概念的项目自动化构建工具。它使用一种基于 Groovy 的特定领域语言来声明项目设置,而不是传统的 XML。Gradle 就是工程的管理,帮咱们作了依赖、打包、部署、发布、各类渠道的差别管理等工做。java
一款最新的,功能最强大的构建工具,用它逼格更高android
使用程序代替传统的XML配置,项目构建更灵活apache
丰富的第三方插件,让你为所欲为使用编程
Maven、Ant能作的,Gradle都能作,可是Gradle能作的,Maven、Ant不必定能作。api
刚开始的时候,我对 Gradle 和 Groovy 傻傻分不清楚,觉得都是一种语言。后来才懂了,gradle 是一个构建工具,使用的语言是 Groovy。闭包
下面进入实战。maven
首先为了使用 gradle,你们能够在 Android studio 新建一个 Android 工程。使用其余 IDE 或者须要配置的环境的朋友,能够本身百度相关文章。编程语言
文章示例基于 Gradle 5.1.1 构建的。 ide
Gradle 中的全部内容都基于两个基本概念:项目和任务。
每一个 Gradle 构建都由一个或多个项目组成。项目表明什么取决于您在 Gradle 中所作的事情。例如,一个项目可能表明一个 JAR 库或一个 Web 应用程序。它可能表示从其余项目产生的 JAR 组装而成的发行版 ZIP。项目不必定表明要构建的事物。它可能表示要完成的事情,例如将应用程序部署到暂存或生产环境。暂时不要担忧这彷佛还不清楚。Gradle 的按惯例构建支持为项目的定义添加了更具体的定义。
每一个项目由一个或多个任务组成。任务表明构建执行的一些原子工做。这多是编译某些类,建立 JAR,生成 Javadoc 或将一些存档发布到存储库。
如今,将研究在一个项目中构建一些简单的任务。后面的章节将介绍处理多个项目,以及有关处理项目和任务的更多信息。
一样,也是先从 hello world 入门。您可使用如下 gradle
命令运行 Gradle 构建。该 gradle
命令在当前目录中查找名为 build.gradle
的文件。这个 build.gradle
文件称为构建脚本,尽管严格来讲,它是一个构建配置脚本,咱们将在后面看到。构建脚本定义项目及其任务。
要尝试此操做,请建立如下名为的构建脚本 build.gradle
。
// build.gradle task hello { doLast { println 'Hello world!' } }
在项目的移至包含的目录并使用如下命令执行构建脚本:
./gradlew -q hello // Android 用户在根目录使用 ./gradlew
gradle -q hello // 非 Android 用户使用 gradle
使用 -q
命令行选项运行。这将取消 Gradle 的日志消息,所以仅显示任务的输出。这样可使示例输出更加清晰。若是不想,则不须要使用此选项。
后面直接将执行构建脚本的命令放在注释前,不在单行做为展现了。
在这里,你将看到了如何使用字符串做为任务名称来定义任务。此样式有一些变体,您可能须要在某些状况下使用。
task('hello') { doLast { println "hello" } } task('copy', type: Copy) { from(file('srcDir')) into(buildDir) }
上面建立了两个任务分别是 hello 和 copy。有一种定义任务的替代语法,您可能更喜欢使用:
tasks.create('hello') { doLast { println "hello" } } tasks.create('copy', Copy) { from(file('srcDir')) into(buildDir) }
上面一样建立了两个任务分别是 hello 和 copy。 最后,Groovy 和 Kotlin DSL 有特定于语言的语法:
// Using Groovy dynamic keywords task(hello) { doLast { println "hello" } } task(copy, type: Copy) { from(file('srcDir')) into(buildDir) }
Gradle 的构建脚本为您提供了 Groovy 和 Kotlin 的所有功能。做为一个开胃菜,看看这个:在Gradle的任务中使用 Groovy 或 Kotlin :
//
task upper { doLast { String someString = 'mY_nAmE' println "Original: $someString" println "Upper case: ${someString.toUpperCase()}" } }
gradle -q upper
// gradle -q count
task count { doLast { 4.times { print "$it " } } }gradle -q count
任务之间能够具备依赖性,关键字 dependsOn :
// gradle -q intro task hello { doLast { println 'Hello world!' } } task intro { dependsOn hello doLast { println "I'm Gradle" } }
上面的依赖是依赖的任务先声明,而后再进行依赖,还有一种依赖是懒惰性依赖,被依赖的任务能够后面再声明,可是若是不声明的会报错:
// gradle -q taskX task taskX { dependsOn 'taskY' doLast { println 'taskX' } } task taskY { doLast { println 'taskY' } }
任务 taskX 依赖的任务 taskY 是后声明的。
您能够经过多种方式定义任务的依赖关系。在“ 任务依赖项”中,介绍了使用任务名称定义依赖项。任务名称能够引用与该任务在同一项目中的任务,也能够引用其余项目中的任务。要引用另外一个项目中的任务,请在任务名称前添加其所属项目的路径。如下是添加从 projectA:taskX
到的依赖的示例 projectB:taskY
:
project('projectA') { task taskX { dependsOn ':projectB:taskY' doLast { println 'taskX' } } } project('projectB') { task taskY { doLast { println 'taskY' } } }// gradle -q taskX
此处的 projectA,projectB 要改为你项目中的名字,简单来讲,就是不一样层级的任务也是能够相互依赖的。
Groovy 或 Kotlin 的功能可用于定义任务之外的其余功能。例如,您也可使用它来动态建立任务。
// gradle -q task1 4.times { counter -> task "task$counter" { doLast { println "I'm task number $counter" } } }
上述建立了 4 个 task,分别是 task0,task1,task2,task3。
任务建立后,就能够经过 API 对其进行访问。例如,您能够在运行时为任务动态添加依赖项。
// gradle -q task0 4.times { counter -> task "task$counter" { doLast { println "I'm task number $counter" } } } task0.dependsOn task2, task3
或者,您能够将行为添加到现有任务。
task hello { doLast { println 'Hello Earth' } } hello.doFirst { println 'Hello Venus' } hello.configure { doLast { println 'Hello Mars' } } hello.configure { doLast { println 'Hello Jupiter' } }// gradle -q hello
调用 doFirst
和 doLast
能够执行屡次。他们将操做添加到任务操做列表的开头或结尾。执行任务时,将按顺序执行操做列表中的操做。
访问现有任务有一种方便的表示法。每一个任务均可以做为构建脚本的属性来使用:
// gradle -q hello task hello { doLast { println 'Hello world!' } } hello.doLast { println "Greetings from the $hello.name task." }
例子中,经过获取任务的名字能够知道这个是来自于 task hello 的任务所作的事。这样能够提升代码的可读性,尤为是在使用插件提供的任务(例如compile
任务)时。
您能够将本身的属性添加到任务上。要添加名为的属性 myProperty
,并为 ext.myProperty 设置
初始值。就能够像预约义的任务属性同样读取和设置属性。
// gradle -q printTaskProperties task myTask { ext.myProperty = "myValue" } task printTaskProperties { doLast { println myTask.myProperty } }
若是未指定其余任务,则Gradle容许您定义一个或多个默认任务。
// gradle -q defaultTasks 'clean', 'run' task clean { doLast { println 'Default Cleaning!' } } task run { doLast { println 'Default Running!' } } task other { doLast { println "I'm not a default task!" } }
这等效于运行 gradle clean run
。在多项目构建中,每一个子项目均可以有其本身的特定默认任务。若是子项目未指定默认任务,则使用父项目的默认任务(若是已定义)
正如咱们稍后将详细描述的(请参阅 Build Lifecycle),Gradle具备配置阶段和执行阶段。在配置阶段以后,Gradle 知道应该执行的全部任务。Gradle 为您提供了一个利用此信息的机会。一个用例是检查发布任务是否在要执行的任务中。以此为基础,您能够为某些变量分配不一样的值。
在如下示例中,distribution
和 release
任务的执行致使 version
变量的值不一样。
// gradle -q distribution // gradle -q release task distribution { doLast { println "We build the zip with version=$version" } } task release { dependsOn 'distribution' doLast { println 'We release now' } } gradle.taskGraph.whenReady { taskGraph -> if (taskGraph.hasTask(":release")) { version = '1.0' } else { version = '1.0-SNAPSHOT' } }
能够发现,此处,执行不一样的 task 具备不一样结果。
若是构建脚本须要使用外部库,则能够将它们添加到构建脚本自己中的脚本的类路径中。您可使用 buildscript()
方法执行此操做,并传入一个声明构建脚本类路径的块。
buildscript { repositories { mavenCentral() } dependencies { classpath group: 'commons-codec', name: 'commons-codec', version: '1.2' } }
buildscript()
方法中的代码块将构成 ScriptHandler 实例。您能够经过向 classpath
配置添加依赖项来声明构建脚本类路径。这与您声明 Java 编译类路径的方式相同。您可使用除项目依赖项之外的任何 依赖项类型。
声明了构建脚本类路径后,就能够像在该类路径上的任何其余类同样使用构建脚本中的类。如下示例将添加到前面的示例中,并使用构建脚本类路径中的类。
//
import org.apache.commons.codec.binary.Base64 buildscript { repositories { mavenCentral() } dependencies { classpath group: 'commons-codec', name: 'commons-codec', version: '1.2' } } task encode { doLast { def byte[] encodedString = new Base64().encode('hello world\n'.getBytes()) println new String(encodedString) } }gradle -q encode
这里先是添加了依赖项,而后再建立了一个任务,引用了依赖项中的类来实现对字符串的加密。
您一般须要找到在构建文件中定义的任务,例如,对其进行配置或将其用于依赖项。有不少方法能够作到这一点。首先,就像定义任务同样,Groovy和Kotlin DSL具备特定于语言的语法
task hello task copy(type: Copy) // Access tasks using Groovy dynamic properties on Project println hello.name println project.hello.name println copy.destinationDir println project.copy.destinationDir
任务也能够经过 tasks
集合得到。
task hello task copy(type: Copy) println tasks.hello.name println tasks.named('hello').get().name println tasks.copy.destinationDir println tasks.named('copy').get().destinationDir
您可使用 tasks.getByPath()
方法使用任务的路径从任何项目访问任务。您能够 getByPath()
使用任务名称,相对路径或绝对路径来调用该方法。
project(':projectA') { task hello } task hello println tasks.getByPath('hello').path println tasks.getByPath(':hello').path println tasks.getByPath('projectA:hello').path println tasks.getByPath(':projectA:hello').path
与 Task
在建立后配置变量的可变属性相反,您能够将参数值传递给 Task
类的构造函数。为了将值传递给 Task
构造函数,您必须使用注释相关的构造函数 @javax.inject.Inject
。
class CustomTask extends DefaultTask { final String message final int number @Inject CustomTask(String message, int number) { this.message = message this.number = number } }
而后,您能够建立一个任务,并在参数列表的末尾传递构造函数参数。
tasks.create('myTask', CustomTask, 'hello', 42)
task myTask(type: CustomTask, constructorArgs: ['hello', 42])
上述两种方法均可以。在全部状况下,做为构造函数参数传递的值都必须为非 null。若是您尝试传递一个 null
值,Gradle 将抛出一个 NullPointerException
指示,指出哪一个运行时值是 null
。
您能够在任务中添加描述。执行 gradle tasks 时将显示此描述。
// gradle tasks task copy(type: Copy) { description 'Copies the resource directory to the target directory.' from 'resources' into 'target' include('**/*.txt', '**/*.xml', '**/*.properties') }
有时您想替换任务。例如,若是要将 Java 插件添加的任务与其余类型的自定义任务交换。您可使用如下方法实现此目的:
// gradle -q copy task copy(type: Copy) task copy(overwrite: true) { doLast { println('I am the new one.') } }
定义新任务时,必须将 overwrite
属性设置为 true。不然,Gradle 会引起异常,说该名称的任务已经存在。
Gradle 提供了多种方法来跳过任务的执行。
您可使用该 onlyIf()
方法将谓词附加到任务。仅当谓词评估为 true 时,才执行任务的动做。您将谓词实现为闭包。闭包做为参数传递给任务,若是任务应执行,则应返回 true;若是应跳过任务,则应返回 false。在即将执行任务以前就对谓词进行评估。
//
task hello { doLast { println 'hello world' } } hello.onlyIf { !project.hasProperty('skipHello') }gradle hello -PskipHello
若是不能用谓词来表示跳过任务的逻辑,则可使用 StopExecutionException。若是某个动做引起了此异常,则将跳过该动做的进一步执行以及该任务的任何后续动做的执行。构建继续执行下一个任务。
// gradle -q myTask task compile { doLast { println 'We are doing the compile.' } } compile.doFirst { // Here you would put arbitrary conditions in real life. // But this is used in an integration test so we want defined behavior. if (true) { throw new StopExecutionException() } } task myTask { dependsOn('compile') doLast { println 'I am not affected' } }
每一个任务都有一个 enabled
默认为的标志 true
。将其设置为 false
阻止执行任何任务动做。禁用的任务将标记为“跳过”。
task disableMe { doLast { println 'This should not be printed if the task is disabled.' } } disableMe.enabled = false
每一个任务都有一个 timeout
可用于限制其执行时间的属性。当任务达到超时时,其任务执行线程将被中断。该任务将被标记为失败。终结器任务仍将运行。若是 --continue
使用,其余任务能够在此以后继续运行。不响应中断的任务没法超时。Gradle 的全部内置任务均会及时响应超时
task hangingTask() { doLast { Thread.sleep(100000) } timeout = Duration.ofMillis(500) }
有时您想执行一个任务,该任务的行为取决于较大或无限数量的参数值范围。提供此类任务的一种很是好的表达方式是任务规则:
// gradle -q pingServer1 tasks.addRule("Pattern: ping<ID>") { String taskName -> if (taskName.startsWith("ping")) { task(taskName) { doLast { println "Pinging: " + (taskName - 'ping') } } } }
规则不只在从命令行调用任务时使用。您还能够在基于规则的任务上建立 dependsOn 关系:
// gradle -q tasks.addRule("Pattern: ping<ID>") { String taskName -> if (taskName.startsWith("ping")) { task(taskName) { doLast { println "Pinging: " + (taskName - 'ping') } } } } task groupPing { dependsOn pingServer1, pingServer2 }groupPing
若是运行“ gradle -q tasks
”,将找不到名为“ pingServer1
”或“ pingServer2
” 的任务,可是此脚本正在根据运行这些任务的请求执行逻辑。
当计划运行终结任务时,Finalizer tasks 会自动添加到任务图中。
//
task taskX { doLast { println 'taskX' } } task taskY { doLast { println 'taskY' } } taskX.finalizedBy taskYgradle -q taskX
即便终结任务失败,也将执行 Finalizer tasks。
// gradle -q taskX task taskX { doLast { println 'taskX' throw new RuntimeException() } } task taskY { doLast { println 'taskY' } } taskX.finalizedBy taskY
运行结果:
Output of gradle -q taskX > gradle -q taskX taskX taskY FAILURE: Build failed with an exception. * Where: Build file '/home/user/gradle/samples/groovy/build.gradle' line: 4 * What went wrong: Execution failed for task ':taskX'. > java.lang.RuntimeException (no error message) * Try: Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights. * Get more help at https://help.gradle.org BUILD FAILED in 0s
在构建建立不管构建失败仍是成功都必须清除的资源的状况下,终结器任务颇有用。这种资源的一个示例是一个Web容器,它在集成测试任务以前启动,而且即便某些测试失败,也应始终将其关闭。
要指定终结器任务,请使用 Task.finalizedBy(java.lang.Object ...) 方法。此方法接受Task实例,任务名称或 Task.dependsOn(java.lang.Object…) 接受的任何其余输入。
到此,关于 task 的讲解到这里就结束了。