Gradle系列第(二)篇---Gradle编程主要对象

版权声明:本文为LooperJing原创文章,转载请注明出处!

学习Gradle前,需要有一个Groovy语言的基础,以免被Groovy的语法困扰,反而忽略了Gradle的知识,可以大致看下上篇文章Gradle系列第(一)篇---Groovy语法初探1。作为一个菜鸟,很想知道 Gradle 的脚本怎么去写,也看了很多网上的文章,大多都是从脚本的角度来介绍Gradle,给我的感觉就是,只记住参数怎么配置,却不知道它们都是函数调用,有相关API对应的。比如我们很常见的一行代码 apply plugin: 'com.android.application' 是什么意思呢?原来一个 build.gradle 对应一个 Project , apply 是一个Project 的一个函数 ,这段代码其实就是调用了project对象的apply方法,传入了一个以plugin为key的map。完整写出来就是这样的:project.apply([plugin: 'com.android.application'])。这样就明白了。所以我们先要学习Gradle的API,然后才能熟练用Gradle构建项目。

1、Project、Task、Action的关系

创建Android项目的时候,每一个项目中都有一个build.gradle文件,我们称build.gradle文件为构建脚本,后面你会了解到这个构建脚本定义了一个project和一些默认的task,在解析 Gradle 的编译过程之前我们需要理解在 Gradle 中非常重要的两个对象。Project和Task。每个项目的编译至少有一个 Project,一个 build.gradle就代表一个project,每个project里面包含了多个task,每个task代表了构建过程当中的一个原子性操作,比如编译,打包,生成javadoc,发布等这些操作。task 里面又包含很多action,action是一个代码块,里面包含了需要被执行的代码。

2、Gradle对象

Gradle基于Groovy,Groovy又基于Java。所以,Gradle执行的时候和Groovy一样,会把脚本转换成Java对象。Gradle主要有三种对象,这三种对象和三种不同的脚本文件对应,在gradle执行的时候,会将脚本转换成对应的对象:
Gradle对象:当我们执行gradle xxx或者什么的时候,gradle会从默认的配置脚本中构造出一个Gradle对象。在整个执行过程中,只有这么一个对象。Gradle对象的数据类型就是Gradle。我们一般很少去定制这个默认的配置脚本。
Project对象:每一个build.gradle会转换成一个Project对象。
Settings对象:每一个settings.gradle都会转换成一个Settings对象。

既然当我们执行gradle xxx或者什么的时候,gradle会从默认的配置脚本中构造出一个Gradle对象,那么看一看Gradle对象的信息有哪些。在build.gradle文件中定义一个Task,如下:

task printGradleInfo{
    println "----------------------------------------------- "
    println "In posdevice, gradle id is " +gradle.hashCode()
    println "Home Dir:" + gradle.gradleHomeDir
    println "User Home Dir:" + gradle.gradleUserHomeDir
    println "Parent: " + gradle.parent
}

执行gradlew -q printGradleInfo
输出

F:\StudyProject\GradleTest2>gradlew -q printGradleInfo
In posdevice, gradle id is 635573988
Home Dir:C:\Users\wangjing\.gradle\wrapper\dists\gradle-2.14.1-all\8bnwg5hd3w55i
User Home Dir:C:\Users\wangjing\.gradle
Parent: null

如果你在打印gradle的hashCode,得到的輸出也是635573988,也验证了在整个执行过程中,只有这么一个对象

3、Gradle的生命周期

项目结构.png

如上图,执行gradlew -q project
输出

Root project 'GradleTest'
+--- Project ':app'
+--- Project ':library'
\--- Project ':library2'

每一个Library和每一个App都是单独的Project。根据Gradle的要求,每一个Project在其根目录下都需要有一个build.gradle。build.gradle文件就是该Project的编译脚本。因为包含了多个项目,所以还要有一个setting.gradle用于多项目的构建。Gradle的生命周期总共分成三个阶段,初始化阶段,配置阶段,执行任务阶段。首先是初始化阶段,这个时候settings.gradle会执行。初始化的下一个阶段是配置阶段。配置阶段的目标是解析每个project中的build.gradle,其内部的任务也会被添加到一个有向图里,用于解决执行过程中的依赖关系。在上图中,gradle的解析顺序是:rootproject 的setting.gradle,然后是rootproject的build.gradle,然后是各个subproject。最后一个阶段就是执行任务了,你在gradle xxx中指定什么任务,gradle就会将这个xxx任务链上的所有任务全部按依赖顺序执行一遍!
gradle整个编译过程都是可控的,通过实现TaskExecutionListener和BuildListener可以对整个编译过程进行监听。下面的代码打印了一下task的名字。

gradle.addListener(new LifecycleListener())
class LifecycleListener implements  TaskExecutionListener,BuildListener{
    @Override
    void buildStarted(Gradle gradle) {

    }

    @Override
    void settingsEvaluated(Settings settings) {

    }

    @Override
    void projectsLoaded(Gradle gradle) {

    }

    @Override
    void projectsEvaluated(Gradle gradle) {

    }

    @Override
    void buildFinished(BuildResult result) {

    }

    @Override
    void beforeExecute(Task task) {

        println("beforeExecute "+task.name)
    }

    @Override
    void afterExecute(Task task, TaskState state) {
        println("afterExecute  name="+task.name+" state="+state.toString() )
    }
}

输出结果:

beforeExecute printGradleInfo
afterExecute  name=printGradleInfo state=org.gradle.api.internal.tasks.TaskState

4、Project

一般app.build文件的第一行是apply plugin: 'com.android.application',这句话是什么意思,刚刚解释过,我们之前说在 Gradle 中构建脚本定义了一个项目(project)。在构建的每一个项目中,Gradle 创建了一个Project类型的实例,并在构建脚本中关联此Project对象。并且Project接口是你在 Gradle API 中访问一切 的入点,当构建脚本执行时,它会配置此Project对象。调用project的api来获取和项目有关的信息。

task queryInfo<<{
    println name
    println project.name
}

执行命令gradlew -q queryInfo
输出

queryInfo
app

第一个获取的是任务名称,第二个获取的是Project名称,如果把queryInfo中的 println name放在外面,他会打印项目名称。

println name
task check<<{
 println project.name
}
app
app

查询项目的项目信息:

task queryProjectInfo<<{
    //项目名
    println project.name
    //项目相对路径
    println project.path
    //项目描述
    println project.description
    //项目的绝对路径
    println project.projectDir
    //项目的build文件绝对路径
    println project.buildDir
    //项目所在的group
    println project.group
    //项目的版本号
    println project.version
    //项目的ant对象
    println project.ant
}

执行命令gradlew -q queryInfo
输出

app
:app
null
E:\Programs\android_studio\GradleTest\app
E:\Programs\android_studio\GradleTest\app\build
GradleTest
unspecified
org.gradle.api.internal.project.DefaultAntBuilder@3f1ae6ad

还有若干方法的使用

比如,在解析setting.gradle之后,开始解析build.gradle之前,这里如果要干些事情可以写在beforeEvaluate。

在所有build.gradle解析完成后,开始执行task之前,此时所有的脚本已经解析完成,task,plugins等所有信息可以获取,task的依赖关系也已经生成,如果此时需要做一些事情,可以写在afterEvaluate。文档对afterEvaluate(closure)的解释是:

Adds a closure to be called immediately after this project has been evaluated. The project is passed to the closure as a parameter. Such a listener gets notified when the build file belonging to this project has been executed. A parent project may for example add such a listener to its child project. Such a listener can further configure those child projects based on the state of the child projects after their build files have been run.

举个列子:过滤掉一些我不想执行的task.

def disableDebugBuild(){
    //project.tasks包含了所有的tasks,下面的findAll是寻找那些名字中带debug的Task。
    //返回值保存到targetTasks容器中
    def targetTasks = project.tasks.findAll{task ->
        task.name.contains("Debug")
    }
    //对满足条件的task,设置它为disable。如此这般,这个Task就不会被执行
    targetTasks.each{
        println"disable debug task  :${it.name}"
        it.setEnabled false
    }
}

project.afterEvaluate{
    disableDebugBuild()
}

又比如

apply plugin: 'com.android.application'  的原形是
project.apply([plugin: 'com.android.application'])
dependencies {
       compile 'com.google.code.gson:gson:2.3'
}

原形:

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

看看下面的图,Project的方法和属性很多

这是Project官方文档:https://docs.gradle.org/current/dsl/org.gradle.api.Project.html

project的方法.png

5、Task

如果你想知道你多少tasks可以用,直接运行gradlew tasks,其会为你展示所有可用的tasks。当你创建了一个Android工程,那么将包含Android tasks,build tasks,build setup tasks,help tasks,install tasks,verification tasks等。

项目构建过程中那么多任务,有些test相关的任务可能根本不需要,可以直接关掉,在build.gradle中加入如下脚本:

tasks.whenTaskAdded { task ->
    if (task.name.contains('AndroidTest')) {
        task.enabled = false
    }
}

tasks会获取当前project中所有的task,enabled属性控制任务开关,whenTaskAdded后面的闭包会在gradle配置阶段完成。

一般我们定义任务的时候采用的是task + 任务名的方式。例如

task hello << {
        println "hello"
}

现在再介绍另外两种方式,和上面的定义是等价的。

task(hello)<<{
        println "hello"
}
task('hello')<<{
        println "hello"
}

gradle还提供了一个tasks容器来创建任务,通过调用create方法:

tasks.create(name:'hello')<<{
        println "hello"
}

如何获取一个任务呢?

将任务看成项目的属性的方式
println tasks.hello.name
println tasks['hello'].name

使用tasks容器来定位
println hello.name
println project.hello.name

tasks.getByPath()方式来获得
println tasks.getByPath('hello').path
println tasks.getByPath(':hello').path

每个Task包含了Action对象的集合。当Task被执行的时候,其内部的Action集合会按次序逐个执行,所以借助doFirst(),doLast()等方法来控制Action在队列中的顺序,同时也是执行的顺序。

task testAction {
    doFirst {
        println("first")
    }
    doLast {
        println("last")
    }
}

输出

first
last

其中对于doLast这个Action还有一个简便的写法

task testAction <<{
    println("last")
}

<<就代表doLast操作

task与task之间是有关联的,关联可以使用dependsOn和finalizedBy。

task A <<{
    println("i am task A")
}
task B <<{
    println("i am task B")
}

A.dependsOn B

执行gradlew -q A
输出:

i am task B
i am task A

如果是 A.finalizedBy B

i am task A
i am task B

相关链接:

Gradle官网https://gradle.org/

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

推荐阅读更多精彩内容