Gradle实战读书笔记之一 Gradle 构建块

Gradle的一些命令记录:

  1. 执行一个指定的任务:
    gradle -q taskName // 如:gradle -q hello
  2. 查看所有任务
    gradle -q tasks
  3. 执行多个任务
    gradle hello hello2
  4. 排除任务的执行 -x:
    gradle hello -x hello2 // 排除hello2执行

org.gradle.api.Project 项目

在Gradle中,一个项目(project)表示一个正在构建的组件(如:一个jar文件,android中,一个aar文件);
Gradle的build.gradle文件相当于Maven的pom.xml,每个build.gradle文件在构建启动后,对应于org.gradle.api.Project的一个实例,并且能够通过project变量使其隐式可用;

Project类接口

一个Project可用创建新的task,添加依赖与配置,实际上这些都是 project接口的方法实现的,如下dependencies 就是调用 Project的 dependencies 方法,并传递闭包;

dependencies {
    provided 'com.android.support:support-annotations:25.3.1'
    provided 'com.android.support:support-v4:25.3.1'
}

构建Project实例中,可通过代码访问Gradle的所有特性,如:task创建、依赖管理等;当访问属性与方法时,不需要使用project变量;

setDescription("myProject")  // 没有显示指定 project变量
println "Description of project $name: " + project.description;  // 访问name与description属性;

在Android开发中,每个module都表示一个Project,有自己独立的 build.gradle脚本;

org.gradle.api.Task 任务

任务task对应的是 org.gradle.api.Task接口;任务的一些重要功能有:任务动作(task action)和任务依赖(task dependency);
任务动作:定义了一个当任务执行时最小的工作单元;
任务依赖:有些时候,某个任务的运行之前需要其他Task先运行;

Task接口

属性

每个ProjectTask实例都提供可通过 getter与setter方法访问的属性,如:项目名,任务名等;
通常,我们需要自定义一些自己的属性;比如:Android的编译版本,minSdk等等;这就用到扩展属性;

扩展属性
在内部,这些属性以键值对的形式存储,添加属性,使用 ext命名空间,在定义扩展属性的时候,需要使用到 ext 命名空间前缀,如下 :

// 此扩展属性信息,定义在一个单独的gradle文件中,使用的使用
// 在项目的根build.gralde文件中 apply from: "文件名"来引用;

// gradle配置信息
ext {
    COMPILE_SDK_VERSION = 25           // 25 23
    BUILD_TOOLS_VERSION = "25.0.2"   // 25.0.2 23.0.2

    MIN_SDK_VERSION = 14
    TARGET_SDK_VERSION = 23     // 这个千万不能改

    // support依赖支持包
    compile_support = [
            design      : "com.android.support:design:25.3.1",
            annotations : "com.android.support:support-annotations:25.3.1",
            appcompat   : "com.android.support:appcompat-v7:25.3.1",
            recyclerview: "com.android.support:recyclerview-v7:25.3.1",
    ]

    // 常用库包
    compile_common = [
            gson: "com.google.code.gson:gson:2.7"
    ]
}

使用的时候,可省略ext前缀,如下:

android {
    compileSdkVersion COMPILE_SDK_VERSION
    buildToolsVersion BUILD_TOOLS_VERSION
    ....

Gradle属性
Gradle属性可通过gradle.properties文件中声明直接添加到项目中,此文件位于 <USER_HOME>/.gradle.properties目录 或 项目的根目录下,这些属性可以通过项目实例访问;
如果一个project有多个module,也只能有一份 gradle.properties文件;

project下的gradle属性文件

比如,在properties文件声明了(最好在本项目的文件做实验,不动全局的,见下解释):
myname=zhaoyubetter
通过直接引用名字来访问
println(myname) // 来进行访问;

注意:在android studio 中,android视图中,显示的gradle.properties文件是全局的,与操作系统的是同一个文件,如下:

gradle.properties全局文件

项目中的 gradle.properties文件需要切换到 project files 视图,才能看到,如下:

项目的gradle.properties文件

使用Task

默认情况下,每个新创建的Task都是org.gradle.api.DefaultTask类型的;

声明task动作
动作(action)就是在task中合适的地方放置构建逻辑,Task接口提供2个相关的方法来声明task动作:doFirst(Closure) (可使用 >>)与 doLast(Closure) (可使用<<左移);

def curVersion = '1.0.0-SNAPSHOT'
task printVersion {
    doLast {
        println ">>>>>>>>>>>>>>>>>>verson:${curVersion}"
    }
}

// or 写成
task printVersion << {
     println ">>>>>>>>>>>>>>>>>>verson:${curVersion}"
}

通过 gradle printVersion 可执行此任务;

访问DefaultTask属性

  • description属性:描述任务的作用;
  • group属性: 用于定义task的逻辑分组;
    创建Task时,可传递,作为参数:
def version = '1.0.0-SNAPSHOT'
task printVersion(group: 'versioning', description: 'Print project version') {
    doFirst {
        println ">>>>> before reading the version"
    }
    doLast {
        println ">>>>> version: $version"
    }
}

// 或者
task printVersion() {
    group = 'versioning'
    description =  'Print project version'

定义Task依赖

dependsOn方法,允许声明依赖一个或多个task,通过task依赖关系图来建模完整的task生命周期;

def group = 'myGroup'
task first << { println ">>>> first" }
task second << { println ">>>> second" }
// 依赖多个任务
task printVersion(group: group, dependsOn: [second, first]) << {
    println ">>>> the version : $version"
}
task third << { println ">>> third" }
third.dependsOn('printVersion')  // 声明依赖时,按名称引用task

输入:gradle -q third, 输出如下:

依赖输出,顺序是乱的

task 依赖的执行顺序
Gradle不能保证依赖的执行顺序,dependsOn只是定义了所依赖的task需要先执行;在gradle中,执行顺序是由task的输入/输出规范自动确定的;

终结器Task

使用 task 的finalizedBy来使用一个特定的终结器:

task first << { println ">>>> first" }
task second << { println ">>>> second" }
first.finalizedBy second        // 执行 gradle -q first 时,会触发 task second

添加任意代码,设置版本信息

gradle就是groovy,所以可以加入一些groovy代码,如下:

  // 版本
  class ProjectVersion {
    int major
    int minor
    int min

    boolean release

    ProjectVersion(int major, int minor, int min) {
        this.major = major
        this.minor = minor
        this.min = min
        this.release = false
    }

    ProjectVersion(int major, int minor, int min, boolean release) {
        this.major = major
        this.minor = minor
        this.min = min
        this.release = release
    }


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

def version = new ProjectVersion(1, 0, 0)
task printVersion << {
    println(" >>>>>> the verson is ${version}")
}

添加task配置块

task配置块会在task动作之前被执行;

  1. 新建配置文件version.properties如下:
major=1
minor=0
min=0
release=false
  1. 添加Gradle配置 loadVersion(没有 <<>> 运算符) ,并读取 properties文件
ext.versionFile = file('version.properties')
// loadVersion任务,没有 << or >> 运算符,Gradle称之为 task配置
task loadVersion {
    project.version = readVersion()
}

ProjectVersion readVersion() {
    println(">>>>> reading the version file")
    if(!versionFile.exists()) {
        throw new RuntimeException(">>> Required version file does not exist: ${versionFile.canonicalPath}")
    }

    Properties prop = new Properties()
    versionFile.withInputStream { stream ->
        prop.load(stream)
    }

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

task printVersion << {
    println(" >>>>>> the verson is ${version}")     // 访问project的属性version
}

Gradle构建生命周期阶段
执行gradle构建,会运行三个不同的生命周期阶段:初始化、配置和执行;
如下图:

Gradle构建生命周期的构建阶段的顺序
  1. 初始化阶段:gradle为项目创建一个Project实例;
  2. 配置阶段:Gradle构造一个模型来表示任务,此阶段用来为项目或指定的task设置所需的配置;项目的每一次构建的任何配置代码都可以被执行,即使只执行 gradle tasks;
  3. 执行阶段:执行task动作;执行的顺序由他们的依赖决定;如果任务被认为没有修改过,将跳过;

声明task的inputs和outputs

Gradle通过比较2个构建task的inputs和outputs来决定是否是最新的;当 inputs和outputs不同时,task才运行,否则跳过;
输入可以是一个目录,文件或属性等,一个task的输出是通过一个目录或1~n个文件来定义的;
inputs与outputs在DefaultTask类中,被定义为属性或者直接子类来表示;

DefaultTask类定义的task的inputs和outputs

创建Task用来发布产品版本,并自动修改配置文件

// 产品发布版本
task makeReleaseVersion(group: 'versioning', description: 'Makes project a reelase version.') << {
    version.release = true
    // ant task 的propertyfile 提供便利的方式修改属性文件 (version.properties)
    ant.propertyfile(file: versionFile) {
        entry(key: 'release', type: 'string', operation: '=', value: 'true')
    }
}

通过运行 :gralde makeReleaseVersion 可以发现配置文件,确实修改了;

但Gradle并不知道 我们的 是发布版本,为了解决,需要声明它的inputs与outputs

task makeReleaseVersion(group: 'versioning', description: 'Makes project a reelase version.') {
    // 在配置阶段声明 inputs/outputs
    inputs.property('release', version.release)   // 声明版本的release属性作为输入
    outputs.file versionFile                      // 由于版本文件被修改,所以他被声明最为输出文件属性

    doLast {    // 闭包为动作代码
        version.release = true
        ant.propertyfile(file: versionFile) {
            entry(key: 'release', type: 'string', operation: '=', value: 'true')
        }
        println(">>>>>> makeReleaseVersion")
    }
}

task的inputs和outputs是在配置阶段执行的用来连接task依赖,需要在配置块中被定义,而且需要确保赋给 inputs和outputs的值在配置阶段是可访问的;
如果需要编程获得输出,可通过 TaskOutputs上的upToDateWhen(Closure)方法实现,此方法是在执行阶段执行的;如果闭包返回true,则认为该task时最新的;

如果2次执行 gradle makeReleaseVersion 后一次gradle知道项目版本被设置为发布版本了,并自动跳过第二次执行;

UP-TO-DATE

使用自定义Task

自定义Task包含2个组件:自定义的task类(封装逻辑行为)与真实的task(提供用于配置行为的task类所暴露的属性值);gradle称之为增强型task;
可维护性是编写自定义task类的优势之一,毕竟操作的是一个实际的类;

编写自定义Task

继承自 DefaultTask,使用注解表示输入输出;

class ReleaseVersionTask extends DefaultTask {

    @Input Boolean release      // 注解声明输入属性 release
    @OutputFile File destFile   // 定义输出文件

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

 // 使用注解声明将被执行的方法 ,
 // 加上这个action的作用是当执行这个task的时候会自动执行这个方法
    @TaskAction    
    void start() {
        project.version.release = true
        ant.propertyfile(file: versionFile) {
            entry(key: 'release', type: 'string', operation: '=', value: 'true')
        }
    }
}

使用自定义Task
在构建脚本中,创建一个ReleaseVersionTask类型的task,并且通过它的属性赋值来设置输入和输出,如:

// 增強型task暴露的属性单独赋值
task makeReleaseVersion2(type: ReleaseVersionTask) {
    release = version.relase
    destFile = versionFile
}

task规则

某些时候,编写的task可能做着相同的事情,如下版本的管理task任务:

// 主版本+1
task incrementMajorVersion(group: 'versioning', description: 'Increments project major version.') << {
    String currentVersion = version.toString()
    ++version.major
    String newVersion = version.toString()
    logger.info "Incrementing major project version: $currentVersion -> $newVersion"

    ant.propertyfile(file: versionFile) {
        entry(key: 'major', type: 'int', operation: '+', value: 1)
    }
}

// 次版本+1
task incrementMinorVersion(group: 'versioning', description: 'Increments project major version.') << {
    String currentVersion = version.toString()
    ++version.minor
    String newVersion = version.toString()
    logger.info "Incrementing minor project version: $currentVersion -> $newVersion"

    ant.propertyfile(file: versionFile) {
        entry(key: 'minor', type: 'int', operation: '+', value: 1)
    }
}

分别运行命令:gradle incrementMinorVersion -i ,可以看到输出;
但上面的是否可以改进呢?

task规则的命名模式

根据task名称模式执行特定的逻辑,模式由2部分组成:
task名称的静态部分 与 一个占位符,他们联合起来组成一个命令;
如上面的,像:increment<Classifier>Version

Gralde的一些核心插件,利用了task规则,如:clean<TaskName>;

声明task规则

首先需要获得对TaskContainer的引用,然后可调用其addRule(String, Closure) 方法,通过project的getTasks()方法获取 TaskContainer引用;
使用task规则,实现上线动作:

// 使用 taskContainer 添加规则
tasks.addRule("Pattern: increment<Classifier>Version - Increments the project version.") {
    String taskName ->
        if (taskName.startsWith('increment') && taskName.endsWith('Version')) {
            task(taskName) << {  // 符合命名模式的task动态添加doLast方法
                String classifier = (taskName - 'increment' - 'Version'.toLowerCase())  // 提取
                String currentVersion = version.toString()

                switch (classifier) {
                    case 'major': ++version.major
                        break
                    case 'minor': ++version.minor
                        break
                    default: throw new GradleException("Invalid version type '$classifier', Allowed " +
                            " types: ['Major','Minor']")
                }

                String newVersion = version.toString()
                logger.info "Incrementing $classifier project version: $currentVersion -> $newVersion"
                ant.propertyfile(file: versionFile) {
                    entry(key: classifier, type: 'int', operation: '+', value: 1)
                }
            }
        }
}

通过运行gradle tasks会列出一个具体的tasks组rules:

tasks Rules 组

task 规则不能分组,其显示在 Rules组下;

我们运行命令:gradle incrementMajorVersion -i,输出如下:

执行task的分组任务的输出

在buildSrc目录下构建代码

可以将构建脚本的一些groovy类:如上面的ProjectVersion和自定义的task ReleaseVersionTask,这些适合移动到项目的 buildSrc目录下;
Gradle在buildSrc目录下使源文件结构标准化,Java代码在 src/main/java,Groovy代码放在src/main/groovy目录下;这些目录下代码会自动编译,并添加到Gradle构建脚本的classpath中;

Android Studio 中在项目的根目录,建立buildSrc文件夹,并sync一下工程,然后新增文件夹路径 src/main/groovy/包名, 就可以创建groovy脚本了;

buildSrc项目出来了

在当前 module下对应的 build.gradle文件,直接引入 buildSrc 下的类,这样Task定义与build.gradle分离了;

import com.better.ProjectVersion2
import com.better.ReleaseVersionTask2

总结:

了解一下Task的工作细节、自定义Task,也了解一些Task配置与Task动作之间的区别;最后我们在buildSrc中,实现自定义Task的分离,将task独立出来;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容

  • 这篇文章讲给大家带来gradle打包系列中的高级用法-自己动手编写gradle插件。我们平常在做安卓开发时,都会在...
    呆萌狗和求疵喵阅读 15,971评论 22 80
  • 本篇主要是个人学习gradle的笔记总结 一.开始之前 1. 为什么学习Gradle 采用DSL(Doma...
    zyq_neuq阅读 1,485评论 2 12
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,591评论 18 139
  • 前一段时间看到不少人在技术论坛里问「刚学 Android 不久,对 Gradle 不懂,看了很多资料依然一知半解」...
    f9dd77add98e阅读 3,501评论 1 8
  • 前言 为什么需要学Gradle? Gradle 是 Android 现在主流的编译工具,虽然在Gradle 出现之...
    真笨笨鱼阅读 1,488评论 0 0