Grovvy初探

Grovvy对于Gradle,就好比Java对于Android。了解一些基本的Grovvy知识,对于掌握Gradle是非常有必要的。

Grovvy特点

Grovvy是一种基于JVM的动态语言,说简单点,就是可以在Java虚拟机上运行的脚本语言。Grovvy的历史已经很悠久了,随着Android Studio使用Gradle作为编译工具而逐渐被广大开发者熟知。

大部分Android开发者使用的是Java语言。如果你会另一种脚本语言例如Python,那么你学习Grovvy的成本几乎可以忽略不计。因为Grovvy的设计参考了很多脚本语言的特性,同时又与Java有着很多的联系,因此Android程序员可以快速上手Grovvy。这里笔者列举一些简单的Grovvy特点,开发者看完一定可以在其中找到很多语言的影子。

  • 与Java使用一套注释系统,语句末尾不用分号表示结束。
  • 不用指定变量、函数类型,使用动态类型。
  • 不用指定返回值,以最后一行代码作为返回值。
  • 自动为属性添加get\set方法。
  • 大量使用闭包。

……

由此可见,Groovy与很多脚本语言一样,在设计时参考了很多其他语言的优秀特性,这也为开发者上手Groovy提供了便利。另外,学习一门语言,一定要多多查看它的SDK。虽然语言是类似的,但是SDK提供的API还是要从文档中获取。Grovvy的SDK文档地址为http://www.groovy-lang.org/api.html。

当开发者有些功能不知道如何实现时,通过API文档查找关键字,就可以快速找到解决方法。

Grovvy Task

Task是Groovy的核心所在,可以说Task是完成Grovvy任务的最小执行单元。在Grovvy中添加一个Task非常简单,直接指定task[task name]即可。

  1. task helloworld {
  2. println(‘hello world’)
  3. }

Task中还包含具体的执行Action。doFirst和doLast是最常用的两个Action,分别表示在执行之前和执行之后的动作。笔者修改上面的代码,为Task添加这两个Action。

  1. task testAction {
  2.  
  3. println('hello world')
  4.  
  5. doFirst {
  6. println('do First')
  7. }
  8.  
  9. doLast {
  10. println('do Last')
  11. }
  12. }

增加Action之后,执行gradle testAction指令,结果如下所示。

  1. MyGradleTest gradle testAction
  2. hello world
  3. Incremental java compilation is an incubating feature.
  4. :app:testAction
  5. do First
  6. do Last

可以发现,默认的输出(hello world)、指定First的输出(doFirst)和指定Last的输出(doLast)的输出顺序与Action的含义是一致的(但为什么默认的输出与Action的输出不在一起,这里先卖个关子,后面会分析原因)。

对于doFirst和doLast这两个Action来说,它们可以很方便地在编译的生命周期中完成自己的指定操作。同时这些Action还可以通过task.doFirst和task.doLast的方式进行设置,这样的好处是可以不修改原始的Task,而让它获得新的功能。

对于doLast这个Action,还有一种简写方式,代码如下所示。

  1. task testDoLast << {
  2. println('do Last')
  3. }

即通过“<<”符号代表doLast操作,上面的代码等价于下面的代码。

  1. task testDoLast {
  2. doLast {
  3. println('do Last')
  4. }
  5. }

另外对于已经编译过的Task来说,Gradle是具有缓存功能的,开发者可以在日志中看见以下信息。

  1. :app:compileReleaseUnitTestJavaWithJavac UP-TO-DATE

如果一个Task已经编译过了且没有任何修改,那么Gradle就会将这个Task标记为UP-TO-DATE。跳过该Task的执行,从而加快编译速度,这就是Gradle的增量编译功能。

Groovy Task依赖

Grovvy在执行时,默认会按顺序执行,但Grovvy也给Task增加了一个依赖的方法,类似于通过依赖关系连接若干个Task,让原本不在一起的Task能够有一个先后执行关系,这就是Task依赖。

在Grovvy中,开发者可以通过dependsOn方法实现Task依赖。笔者以一个简单的示例演示如何使用Task依赖,代码如下所示。

  1. task testTask0 << {
  2. println('I am Task0')
  3. }
  4.  
  5. task testTask1 << {
  6. println('I am Task1')
  7. }
  8.  
  9. testTask0.dependsOn testTask1

Grovvy初探 - 图1使用dependsOn的语法有很多,大家可以参考DSL语法手册,这里以最常用的Task.dependsOn为例。同时这里只列举了依赖一个Task的情况,实际上可以依赖多个Task。

在上面的代码中,笔者指定了testTask0依赖于testTask1,那么在执行gradle testTask0指令时,Gradle就不仅仅只会执行testTask0了。因为Gradle在解析时发现testTask0还依赖于testTask1,因此就会一并执行testTask1,如下所示。

  1. :app:testTask1
  2. I am Task1
  3. :app:testTask0
  4. I am Task0

可以发现,即使不指定执行testTask1,但由于testTask0依赖了testTask1,它也会被执行,那么这样的功能到底有什么作用呢?其实它的作用非常明显,就是可以在不影响主线Task流程的基础上,通过dependsOn依赖插入自己的Task,从而实现自己的功能。例如将Build Task依赖于自己定义的Task,让系统执行Build前先执行自己的逻辑。

除了使用dependsOn进行Task的依赖,Gradle还提供了finalizedBy实现Task之间的关联,还是类似上面的例子,代码如下所示。

  1. task testTask0 << {
  2. println('I am Task0')
  3. }
  4.  
  5. task testTask1 << {
  6. println('I am Task1')
  7. }
  8.  
  9. testTask0.finalizedBy testTask1

执行gradle testTask0指令后,结果如下所示。

  1. :app:testTask0
  2. I am Task0
  3. :app:testTask1
  4. I am Task1

finalizedBy的作用是当一个任务结束后,执行其所依赖的行为(Task),在上面的例子中,即testTask0执行结束后,执行testTask1。因此笔者只执行了gradle testTask0,testTask1也会被执行。

与dependsOn一样,finalizedBy也是用来进行依赖关系管理的。通过finalizedBy,可以在一个Task结束后,执行一些清理、回收任务,很好地拓展了原有Task。

Groovy Task的禁用与启用

对于Grovvy来说,一个Project中可以存在非常多的Task。而开发者掌握着这些Task的生杀大权,开发者可以根据自己的需要限制Task的执行,例如如下所示的代码。

  1. task testEnabled << {
  2. println('am I enabled')
  3. }
  4.  
  5. testEnabled.enabled = false

通过enabled属性,笔者禁用了testEnable Task。这时候再执行gradle testEnabled指令,结果如下所示。

  1. :app:testEnabled SKIPPED

可以发现,testEnable指令被Skipped了,testEnable Task并没有被执行。

Grovvy初探 - 图2这里要注意的是,虽然指定的Task被禁用。但其依赖的Task如果没有显式指定禁用,那么依赖的Task是不会被禁用的。

那么这个属性的作用是什么呢?笔者举一个非常简单的例子,在Android Studio中执行gradle build指令时会执行所有的Task,包括测试的Task。这些Task实际上是比较耗时的,因此开发者可以根据自己的需要禁用掉这些Task,提高编译速度。再比如,开发者可以根据开发情况选择执行一个Task而禁用掉另一个Task,这些都是非常方便的。例如在开发过程中,开发者需要将测试相关的Task全部禁用以提高编译的速度,那么可以使用如下所示的代码。

  1. tasks.whenTaskAdded { task ->
  2. if (task.name.contains('test')) {
  3. task.enabled = false
  4. }
  5. }

其中tasks是Gradle的默认对象,包含了所有的Task。当执行到whenTaskAdded领域时,Gradle会对增加进来的每一个Task进行判断,根据其TaskName进行判断是否启用,这就是enabled属性的一个具体例子。

Grovvy Task类型

实际上,笔者在前面的Task讲解中一直没有提到Task的类型。因为不指定Task的类型,就会默认为Default类型。同时开发者可以指定Task的其他类型,比较常用的有Jar和Copy两种。其中Jar,笔者已经在“使用Gradle编译成jar包”一节中介绍过了,下面笔者着重介绍Copy类型,代码如下所示。

  1. task testCopy(type: Copy) {
  2. from 'src/main/res/layout'
  3. into 'src/main/new'
  4. }

执行gradle testCopy指令后,生成新的文件夹,如图4.47所示。

Grovvy初探 - 图3 图4.47 Copy类型

Copy类型的Task与Jar类型的Task使用非常类似,只需指定type为Copy即可。通过from和into参数设置Copy的文件和最终生成的文件。当into指定的文件夹不存在时,Gradle也会自动生成相应的文件夹。另外在Copy类型的Task中,与Jar类型的Task类似,也可以使用exclude\include'*/.xml'参数来设置过滤。

除了系统默认给出的这些Task类型之外,Gradle同样支持自定义Task类型,感兴趣的开发者可以去官方文档上查找相关信息,这里笔者不再赘述。