上一章当中的makeReleaseVersion中的动作逻辑相当简单。当前代码的可维护性显然不是问题。然而,随着项目的增长,原本简单的task需要增加更多的逻辑,此时就有了对类和方法中的代码的结构化需求。应该使用像在常规的产品源代码中习惯使用的编码管理。Gradle不建议使用某种特殊方法编写task。完全可以根据熟悉程度,选择熟悉的编程语法,比如Java、Groovy等基于JVM的语言。

自定义task包含两个组件:
- 自定义的task类,封装了逻辑思维,也被称为任务类型,
- 真实的task,提供了用于配置行为的task类所暴露的属性值。Gradle把这些task称为增强的task.

可维护性和可重用性是自定义task类的优势

编写自定义的task类

Gradle为构建脚本中的每个简单的task都创建了一个DefaultTask类型的实例,当创建一个自定义的task时,需要做的是,创建一个继承DefaultTask的类。比如以下为使用Groovy编写的自定义类ReleaseVersionTask

class ReleaseVersionTask extends DefaultTask {
    @Input Boolean release
    @OutputFile File destFile

    ReleaseVersionTask() {
        group = 'versioning'
        description = 'Makes project a release version.'
    }

    @TaskAction
    void start() {
        project.version.release = true
        ant.propertyfile(file: destFile) {
            entry(key: 'release', type: 'string', operation: '=', value: 'true')
        }
    }
}

在这个类当中,没有使用DefaultTask的属性来声明它的输入和输出,而是使用org.gradle.api.tasks包下的注解。它们不仅与TaskInputs和TaskOutputs方法由相同的效果。在自定义的task当中,使用@Input注解声明输入属性release,使用@outputFile注解定义输出文件。为属性添加输入和输出注解并不是唯一的选择,你也可以为getter方法添加注解。

task输入验证:@Input注解会在配置期间验证属性值,如果值为null,Gradle会抛出TaskValidationException异常。为了允许输入null值,可以添加@Optional注解。

使用自定义task

通过创建一个动作方法暴露它的配置属性,实现了一个自定义的task类。那么该如何使用它呢?在构建脚本当中,需要创建一个ReleaseVersionTask类型的task,并且通过为它的属性赋值来设置输入和输出,如下所示

task makeReleaseVersion(type: ReleaseVersionTask) {
    release = version.release
    destFile = versionFile
}

构建脚本build.gradle中其他代码如下所示,在上一章都已经讲解过

ext.versionFile = file('version.properties')

task loadVersion {
    project.version = readVersion()
}

ProjectVersion readVersion() {
    logger.quiet 'Reading the version file.'

    if (!versionFile.exists()) {
        throw new GradleException("Required version file does not exit: $versionFile.canonicalPath")
    }

    Properties versionProps = new Properties()

    versionFile.withInputStream { stream ->
        versionProps.load(stream)
    }

    new ProjectVersion(versionProps.major.toInteger(), versionProps.minor.toInteger(), versionProps.release.toBoolean())
}

class ProjectVersion {
    Integer major
    Integer minor
    Boolean release

    ProjectVersion(Integer major, Integer minor) {
        this.major = major
        this.minor = minor
        this.release = Boolean.FALSE
    }

    ProjectVersion(Integer major, Integer minor, Boolean release) {
        this(major, minor)
        this.release = release
    }

    @Override
    String toString() {
        "$major.$minor${release ? '' : '-SNAPSHOT'}"
    }
}

以及同目录下的version.properties文件

major=0
minor=1
release=false

在控制台执行任务

android 通过gradle生成自定义buildconfig文件_自定义


而version.properties文件被修改了

android 通过gradle生成自定义buildconfig文件_Boo_02


任务起效了。

自定义task的可重用性

假设你想要在另一个项目当中使用自定义task,在那个项目中,需求是不同的。其版本POJO通过暴露不同的属性来表示版本管理方案,如下所示

class ProjectVersion {
    Integer min
    Integer maj
    Boolean prodReady

    @Override
    String toString() {
        "$maj.$min${prodReady ? '' : '-SNAPSHOT'}"
    }
}

而且此时项目所有者决定命名版本文件为project-version.properties而不是version.properties。如何让增强的task满足这些需求呢?其实你只需要给所暴露的属性赋予不同的值就可以了。自定义的task类可以灵活得处理需求的变化。

task makeReleaseVersion(type: ReleaseVersionTask) {
    release = version.prodReady
    destFile = new File('project-version.properties')
}

结果如下

android 通过gradle生成自定义buildconfig文件_Boo_03


以下为完整的代码

ext.versionFile = file('project-version.properties')

task loadVersion {
    project.version = readVersion()
}

ProjectVersion readVersion() {
    logger.quiet 'Reading the version file.'

    if (!versionFile.exists()) {
        throw new GradleException("Required version file does not exit: $versionFile.canonicalPath")
    }

    Properties versionProps = new Properties()

    versionFile.withInputStream { stream ->
        versionProps.load(stream)
    }

    new ProjectVersion(min: versionProps.major.toInteger(), maj: versionProps.minor.toInteger(), prodReady: versionProps.release.toBoolean())
}

task makeReleaseVersion(type: ReleaseVersionTask) {
    release = version.prodReady
    destFile = new File('project-version.properties')
}

class ReleaseVersionTask extends DefaultTask {
    @Input Boolean release
    @OutputFile File destFile

    ReleaseVersionTask() {
        group = 'versioning'
        description = 'Makes project a release version.'
    }

    @TaskAction
    void start() {
        project.version.prodReady = true
        ant.propertyfile(file: destFile) {
            entry(key: 'release', type: 'string', operation: '=', value: 'true')
        }
    }
}

class ProjectVersion {
    Integer min
    Integer maj
    Boolean prodReady

    @Override
    String toString() {
        "$maj.$min${prodReady ? '' : '-SNAPSHOT'}"
    }
}