《Gradle for Android》笔记(7):创建Task和Plugin

cover

Groovy概览

  • 输出println ‘Hello, Groovy!’
    1. 无方法括号。
    2. 无分号结尾
    3. 不需要namespace。(对比System.out.println
  • 字符串可以使用单引号和双引号,双引号可以嵌入表达式。$作为嵌入表达式标记。

    1
    2
    3
    def name = 'Andy'
    def greeting = "Hello, $name!"
    def name_size "Your name is ${name.size()} characters long."
  • 动态修改代码体:

1
2
def method = 'toString'
new Date()."$method"()
  • 定义类和属性

    1
    2
    3
    4
    5
    6
    7
    class SampleClass {
    String greeting

    String getGreetting() {
    return 'Hello !'
    }
    }
  • Groovy的方法的可访问性默认为public属性的默认为private

  • 属性会自动带有Getter和Setter,同时直接访问属性的话也会调用Getter。(类似OC)。Getter和Setter可以被覆写。
  • def语句用于声明变量,方法。
  • 方法不需要返回值

    1
    2
    3
    4
    def square(def num) {
    num * num
    }
    square 4
  • 闭包实现

    1
    2
    3
    4
    def square = { num ->
    num * num
    }
    square 8
  • 闭包

    1
    2
    3
    4
    5
    Closure square = {
    // 隐含的参数
    it * it
    }
    square 16
  • androiddependences都是闭包类型。

  • 列表

    1
    2
    3
    4
    List list = [1, 2, 3, 4, 5]
    list.each() { element ->
    println element
    }
  • Map

    1
    2
    3
    4
    5
    Map pizzaPrices = [margherita:10, pepperoni:12]
    // 访问元素
    pizzaPrices.get('pepperoni')
    pizzaPrices['pepperoni']
    pizzaPrices.pepperoni

Gradle中的Groovy

  • Gradle中大量使用Groovy的简写形式。

    apply

    1
    2
    3
    4
    // 这个Groovy的简写
    apply plugin: 'com.android.application'
    // 等价于
    project.apply([plugin: 'com.android.application'])

apply:方法名
plugin: 'com.android.application’:Map类型的参数

dependencies

1
2
3
4
project.dependencies({ add('compile', 'com.google.code.gson:gson:2.3', {
// Configuration statements
})
})

Tasks

  • 使用task语法创建Task

    1
    2
    3
    task sayHello {
    println 'Hello!'
    }
  • 以上将创建一个叫做sayHello的Task,但是这个Task是在Task的Gradle构建生命周期的Configuration过程执行的。

    • 生命周期:
      • Initialization 初始化:创建Project实例。每个Module/每个build.gradle文件都对应一个Project实例。
      • Configuration 配置:针对每个Project实例,创建和配置其中的Tasks。Tasks之间的依赖关系就是在这个阶段生成的。
      • Execution 执行:执行特定的Tasks。Task是否执行依赖于启动构建的参数和当前的目录。
    • 因为Configuration是针对Project中的所有Task,所以即便是执行其他Task的时候,sayHello依然会执行。
  • 使用<<语法创建一个在Execution过程执行的Task。

    1
    2
    3
    task sayHello << {
    println 'Hello!'
    }
  • 创建任务的其他写法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // task是`Project`类的方法。
    task(hello) << {
    println 'Hello, world!'
    }

    task('hello') << {
    println 'Hello, world!'
    }

    // tasks是Project类中的TaskContainer的一个对象。
    // create方法接收一个Map和一个闭包作为参数,返回Task对象
    tasks.create(name: 'hello') << {
    println 'Hello, world!'
    }
  • 创建的Task实际上继承自DefaultTaskDefaultTask又继承自AbstractTaskAbstractTaskTask接口的实现类,实现了其所有方法。

  • Task包含一系列Actions对象,Action在Execution阶段依次执行。(即便Task没有使用<<符号声明为Execution阶段的Task,其中的Action依然在Execution阶段执行;但是Task中的代码是在Configuration阶段执行的。)
  • 使用doFirstdoLast添加Action。Action实际上是一个闭包对象。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    task hello {
    println 'Configuration'

    doLast {
    println 'Goodbye'
    }

    doFirst {
    println 'Hello'
    }
    }
  • doFirst总是将Action添加到起始位置,所以后写的Action会在先写的前面执行。doLast类似。

  • Tasks的执行顺序由mustRunAfter()方法指定。
    1
    2
    3
    4
    5
    6
    7
    task task1 << {
    println 'task1'
    }
    task task2 << {
    println 'task2'
    }
    task2.mustRunAfter task1

gradlew task2 task1命令的执行结果为:

1
2
3
4
:task1
task1
:task2
task2

  • mustRunAfter()方法仅指定执行顺序,如果单独先执行task2,后执行task1,顺序将不受mustRunAfter()方法的控制。
  • dependsOn方法用以指定Task之间的依赖,下面代码将使得单独执行task2时,也会先执行task1。

    1
    2
    3
    4
    5
    6
    task2.dependsOn task1
    // 或者在创建task2时指定
    task task2 << {
    dependsOn task1
    println 'task2'
    }
  • 配置发布密钥的task实例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    task getReleasePassword << {
    def password = ''

    if (rootProject.file('private.properties').exists()) {
    Properties properties = new Properties();
    properties.load(rootProject.file('private.properties').newDataInputStream())
    password = properties.getProperty('release.password')
    // 类似Swift和Kotlin的语法
    if (!password?.trim()) {
    password = new String(System.console().readPassword ("\nWhat's the secret password? "))
    }
    }
    }

将密钥保存到private.properties文件中,使用k=v格式保存。

  • 添加依赖。
    1
    2
    3
    4
    5
    6
    tasks.whenTaskAdded { theTask -> 
    if (theTask.name.equals("packageRelease")) {
    // 为所有packageRelease任务添加getReleasePassword依赖
    theTask.dependsOn "getReleasePassword"
    }
    }
  • package是Gradle内置的Task,packageRelease就是针对Release这种构建类型的Task。Assemble会依赖package,所以以上代码使package任务依赖getReleasePassword,就会让Release版本的构建依赖getReleasePassword

    上面代码不适用于包含ProductFlavor的情况。

  • 针对每中构建版本处理

    1
    2
    3
    android.applicationVariants.all {variant ->
    //
    }
  • each内的闭包会在each执行时对每个item都执行一次,而all方法在每当向list中添加item时执行一次。each在Android插件还没有生成变体之前就执行了;all方法在生成变体并添加到list时执行。

  • 修改输出文件名

    1
    2
    3
    4
    5
    6
    7
    8
    android.applicationVariants.all {
    // outputs而不是output的原因,是因为每个构建其实有多个输出。
    // 通常认为只有一个Apk,是因为对Android应用而言,只有一个Apk。
    variant -> variant.outputs.each { output ->
    def file = output.outputFile
    output.outputFile = new File(file.parent, file.name.replace(".apk", "-${variant.versionName}.apk"))
    }
    }
  • 动态创建Task,在Execution步骤之前根据构建配置创建。将生成runXxxYyyZzz类的Tasks。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    android.applicationVariants.all {
    variant -> if (variant.install) {
    tasks.create(name: "run${variant.name.capitalize()}", dependsOn: variant.install) {
    // description会在执行`./gradlew tasks`中展示
    description "Installs the ${variant.description} and runs the main launcher activity."
    doFirst {
    exec {
    executable = 'adb'
    args = ['shell', 'am', 'start', '-n', "${variant.applicationId}/.MainActivity"]
    }
    }
    }
    }
    }

创建插件

  • 文档: Gradle - Plugins
  • 插件可以使用GroovyJava或任何能够运行在JVM上的语言编写。
  • Android插件中的大部分都是Java和Groovy混合编写的。
  • 创建插件的方法:自定义类实现Plugin接口,实现apply方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class RunPlugin implements Plugin<Project> {
    void apply(Project project) {
    project.android.applicationVariants.all {
    variant -> if (variant.install) {
    project.tasks.create(name: "run${variant.name.capitalize()}", dependsOn: variant.install) {
    // Task definition
    }
    }
    }
    }
    }
  • apply plugin:语句的顺序会影响插件之间的依赖关系,上面的插件必须写在Android插件的后面。

  • 插件项目也使用Gradle构建,其build.gradle文件如下:

    1
    2
    3
    4
    5
    6
    7
    apply plugin: 'groovy'

    dependencies {
    compile gradleApi()
    // 本地的Groovy SDK
    compile localGroovy()
    }
  • 项目结构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    plugin
    └── src
    └── main
    ├── groovy
    │ └── com
    │ └── package
    │ └── name
    └── resources
    └── META-INF
    └── gradle-plugins // 插件配置目录,配置文件以插件ID命名
  • 配置文件名称:com.gradleforandroid.RunPlugin

  • 配置文件内容:
    1
    implementation-class=com.gradleforandroid.RunPlugin

其他文章