Android开发教程—Gradle(一)

前言

在前几篇中,讲解了Groovy的基础语法,学习新语法过程总是枯燥的,但为了更好的掌握Gradle,那就必须经过该过程。当然从这一篇将会从零基础开始,开展对Gradle的讲解。

1. 掌握Gradle基础

1.1 环境配置

  • 去官网下载gradle或者从本地用户文件夹下的.gradle/wrapper/dists找到本地缓存的gradle开发工具包 (注意带bin文件夹的这个grade-x.x)
  • 系统属性配置:
  1. 添加GRADLE_HOME:C:\Users\sheji.gradle\wrapper\dists\gradle-6.5-all\gradle-6.5
  2. 添加Path:%GRADLE_HOME%\bin
  • 检查配置

CMD 命令窗口输入 gradle -v,看看是否显示当前Gradle版本

1.2 Hello Gradle!

我们学习任何开发语言,第一个程序都离不开Hello 程序。所以仪式感还是要有的。

  1. 在工程文件夹下,创建一个build.gradle文件:

  2. 在文件里写入代码```
    task hello{
    println 'Hello, Gradle!'
    }

    
    
  3. 打开cmd终端,移动到工程目录下,执行命令:> gradle -q hello

  4. 最终效果

如图所示

当运行成功时,将会自动创建.gradle文件夹。

此时我们继续在这个命令窗口执行命令:gradle wrapper 将会看到

如图所示

在本目录里面,系统自动帮我们创建了一系列文件以及文件夹,这些文件都似曾相识,打开AS和idea对比一下,发现目录里面有的对应编译器都会有。

这就是一个很标准的gradle工程目录结构:

gradlew和gradlew.bat分别是Linux和Windows下的可执行脚本,具体业务逻辑是在/gradle/wrapper/gradle-wrapper.jar中实现,gradlew最终还是使用Java执行这个jar包来执行相关的Gradle操作的。

1.2.1 gradle-wrapper.properties解读

distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

这段代码就是这个文件里面的内容,现在将逐一进行解读

  • distributionBase:下载的Gradle压缩包解压后存储的主目录
  • distributionPath:相对于distributionBase的解压后的Gradle压缩包的路径
  • distributionUrl:Gradle发行版压缩包的下载地址
    • -bin:二进制发布版
    • -all:bin基础上还包含了源码和文档
  • zipStoreBase:同distributionBase,只不过存放的是zip压缩包的
  • zipStorePath:同distributionPath,只不过存放的是zip压缩包的

而 distributionBase对应的值为:GRADLE_USER_HOME

1.2.2 GradleUserHome 属性解读

  • 默认路径在~/.gradle/ ,不建议使用本地maven的m2替代,因为原本的.gradle目录下的模块分的很清晰,功能明确
  • 如果启动时,指定参数,使用别的目录代替GradleUserHome ,后果是每次构建需要重新下载插件与依赖到新的目录
  • 默认情况下,gradle运行时,除了和项目打交道,还有当前项目构建的全新的GradleUserHome目录,所有jar包都得重新下载

1.3 Gradle部分命令行

  • gradlew -?/-h/-help 使用帮助
  • gradlew tasks 查看所有可执行Tasks
  • gradlew --refresh-dependencies assemble 强制刷新依赖
  • gradlew cBC 等价与执行Task cleanBuildCache,这种通过缩写名快速执行任务
  • gradlew :app:dependencies 查找app工程依赖树

2. 熟悉Gradle构建机制

2.1 熟悉setting.gradle的作用

我们继续看 Java、Android工程目录

如图所示

这里我们看到:这个俩个工程目录比刚刚多了个setting.gradle文件,现在来单独说明下这个文件具体有啥作用:

  • Gradle支持多工程构建,使用settings.gradle来配置添加子工程(模块)
  • settings文件在初始化阶段执行,创建Settings对象,在执行脚本时调用该对象的方法
  • Settings.include(String... projectPaths)
    • 将给定的目录添加到项目构建中,':app'表示文件相对路径,相当于'./app'文件夹
    • 多项目架构进行分层,把同层次的子工程放在同一文件夹下便于管理,使用':xxx:yyy'表示

刚刚也说明了,settings文件在初始化阶段执行,那么其他阶段有哪些呢?这就要从Gradle生命周期开始说起了。

2.2 熟悉Gradle生命周期

Gradle生命周期无非就是这三个:Initialization、Configuration、Execution

  • Initialization
    • Gradle支持单项目和多项目构建。在初始化阶段,Gradle确定哪些项目将参与构建,并为每个项目创建Project实例,一般我们不会接触到它。(比如解析settings.gradle)
  • Configuration
    • 配置阶段,解析每个工程的build.gradle文件,创建要执行的任务子集和确定各种任务之间的关系,并对任务的做一些初始化配置。
  • Execution
    • 运行阶段,Gradle根据配置阶段创建和配置的要执行的任务子集,执行任务。

而这三个生命周期又与执行流程息息相关。

2.3 熟悉Gradle执行流程

如图所示

从这张图可知,不同的阶段都有对应方法的调用顺序,在这里我们暂可熟悉这些即可:

  1. 初始化阶段里面的Setting.gradle
  2. 配置阶段的所有
  3. 执行阶段的所有

刚刚已经讲解了setting.gradle的作用,现在接着讲解配置阶段相关的内容。

2.4 熟悉Gradle配置阶段

  1. 解析每个Project中的build.gradle,解析过程中并不会执行各个build.gradle中的task。
  2. 经过Configration阶段,Project之间及内部Task之间的关系就确定了。一个Project包含很多Task,每个Task之间有依赖关系。Configuration会建立一个有向图来描述Task之间的依赖关系,所有Project配置完成后,会有一个回调project.afterEvaluate(),表示所有的模块都已经配置完了。

因为这里配置阶段和Gradle对应的Task(任务列表)相互绑定,所以这里不好拿代码单独解释配置相关的方法,需要结合Gradle任务一起讲,所以这里先忍耐一下,等讲完Task(任务列表)后,再一起解析。

2.5 熟悉Gradle任务

Gradle Task:

  • task是gardle中最小的任务单元,任务之间可以进行复杂的操作(如动态创建任务,多任务间依赖调用等等)。gradle的执行其实就是由各种任务组合执行,来对项目进行构建的
  • 使用gradlew help命令,任何gradle项目都有一个该task,可以执行此命令观察taks执行的流程是否如预期
  • 可以使用工具查看,还可以通过 gradlew tasks 命令查看可运行任务。
    • 使用gradlew tasks --all 命令查看所有任务
    • 使用gradlew A B 命令表示执行任务A和B,支持驼峰简写

注:task是gardle中最小的任务单元,犹如我们现在所常用的金钱单位0.01元一样(别钻牛角尖)。

概念都说了一大堆,接下来就到了愉快的撸码验证环节了。

创建java环境下的Gradle工程项目,进入build.gradle


this.beforeEvaluate {
    println "this.beforeEvaluate"
}
//等同beforeEvaluate
this.gradle.beforeProject {
    println "this.beforeProject"
}

this.afterEvaluate {
    println "this.afterEvaluate"
}
//等同配置阶段完成后
this.gradle.afterProject {
    println "this.afterProject"
}

this.gradle.taskGraph.whenReady{
    println "whenReady"
}

// Gradle3.0 << ,在4.x以上就废弃了
task A {

    println "configuration A.."

    doFirst {
        println "doFirst A.."
    }

    doLast {
        println actions
        println "doLast A1.."
    }

}

task B{
    println "configuration B.."

    doFirst {
        println "doFirst B.."
    }

    doLast {
        println actions
        println "doLast B1.."
    }
}

this.gradle.buildFinished {
    println "buildFinish"
}

代码解析

这里面开头那几个this指向对应的方法,就是刚刚我们执行流程图对应的方法,过后又定义了两个task任务单元,里面分别对不同的action有不同的打印输出,接下来我们看看运行效果:

运行效果一:运行taskA

如图所示

点击右边任务列表中的task或者点击左侧对应task的运行按钮

> Configure project :
configuration A..
configuration B..
this.afterProject
this.afterEvaluate
whenReady

> Task :A
doFirst A..
[org.gradle.api.internal.AbstractTask$ClosureTaskAction@261ae6e0, org.gradle.api.internal.AbstractTask$ClosureTaskAction@76e733fb]
doLast A1..
buildFinish

我们发现在配置阶段,两个Task里面的 configuration 都被打印了出来,随后就打印了配置结束的方法,以及准备进入到执行阶段的whenReady方法。因为运行的A任务,所以随后就进入了taskA的执行阶段,在执行阶段里,优先运行的doFirst 其次是doLast 对应的action,最后如愿的调用了buildFinish方法来结束整个Gradle编译。

不过这里有个小瑕疵:运行发现,配置前的beforeEvaluate、beforeProject对应方法并没有打印出来,如有知情的大佬知道其原因欢迎相互交流。不过这并不影响我们的发挥,因为每个任务的配置代码在构建的时候都会执行。

我们发现,单独运行A或者B的时候,配置阶段都执行了对应任务里面的配置,唯独运行没有一起运行,而我们在使用Android Gradle的时候,都是扒拉扒拉全运行完,就像配置阶段一样。那我们要怎样做呢?

task A {

    println "configuration A.."

    doLast {
        println actions
        println "doLast A1.."
    }

}

task B{
    println "configuration B.."

    doLast {
        println actions
        println "doLast B1.."
    }
}

A.dependsOn B

在这里我这只加了 A.dependsOn B 这段代码,看着意思就有点像,A继承B,运行一下A试试:

> Configure project :
configuration A..
configuration B..

> Task :B
[org.gradle.api.internal.AbstractTask$ClosureTaskAction@213f5745]
doLast B1..

> Task :A
[org.gradle.api.internal.AbstractTask$ClosureTaskAction@70cabab9]
doLast A1..

我们发现,运行A的时候,两者都一起运行了,但是问题来了,当我运行B的时候,又变成了单独运行,而且如果出现多个task怎么办呢?总不能相互 dependsOn 吧?

接下来又对代码进行一系列改造:

task A {

    println "configuration A.."

    doLast {
        println actions
        println "doLast A1.."
    }

}

task B{
    println "configuration B.."

    doLast {
        println actions
        println "doLast B1.."
    }
}

task C {
    println "configuration C.."

    doLast {
        println "doLast C.."
    }
}

task hello2(dependsOn: [A, C, B]) {
    doLast {
        println "doLast hello2"
    }
}

这里我们看出,这里定义了新的任务hello,随后将已存在的task通过数组的形式相互绑定,换句话说,就是定义了一个管理类,将所有零散的任务单元全部管理了其他,想运行所有任务单元,只需要运行这个管理类即可。所以运行一下hello2任务单元试试:

> Configure project :
configuration A..
configuration B..
configuration C..

> Task :A
[org.gradle.api.internal.AbstractTask$ClosureTaskAction@40880123]
doLast A1..

> Task :B
[org.gradle.api.internal.AbstractTask$ClosureTaskAction@111c4be4]
doLast B1..

> Task :C
doLast C..

> Task :hello2
doLast hello2

BUILD SUCCESSFUL in 0s
4 actionable tasks: 4 executed
18:33:17: Task execution finished 'hello2'.


在代码里,我特意将 B和C相互兑换位置,结果发现,运行单位元,就是按照字母排序的方式依次运行。

现在又有新的问题,我们在使用任何对象的时候,使用完了都会被回收掉,那么任务执行完了,是不是也会有对应的回收任务?那该使用什么关键字呢?

接下来继续改造代码:

task A {

    println "configuration A.."

    doLast {
        println actions
        println "doLast A1.."
    }

}

task B{
    println "configuration B.."

    doLast {
        println actions
        println "doLast B1.."
    }
}

task C {
    println "configuration C.."

    doLast {
        println "doLast C.."
    }
}

task hello2(dependsOn: [A, C, B]) {
    doLast {
        println "doLast hello2"
    }
}

task finalized {
    doLast {
        println "clear all tasks"
    }
}

hello2.finalizedBy finalized

这里我们看到,又新增了一个task,里面表示执行清理逻辑,随后使用了 finalizedBy 关键字将,任务总管理和清理任务相互关联,运行效果看看

> Configure project :
configuration A..
configuration B..
configuration C..

> Task :A
[org.gradle.api.internal.AbstractTask$ClosureTaskAction@25b5129e]
doLast A1..

> Task :B
[org.gradle.api.internal.AbstractTask$ClosureTaskAction@74447d23]
doLast B1..

> Task :C
doLast C..

> Task :hello2
doLast hello2

> Task :finalized
clear all tasks

到这里,我们应该大概对Gradle任务有所了解了,每一个任务task都是最小单位,而每个task里都有对应的actions。而Gradle构建就是每一个任务单位相互执行后的结果。

结束语

本篇讲解到这里就结束了,相信你对Gradle基础以及构建机制有所认知,在下一篇里,将会继续对Gradle进行深一步的讲解。

本文转自 https://juejin.cn/post/7022954104670388254,如有侵权,请联系删除。

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

推荐阅读更多精彩内容