gradle基本概念
百度百科:Gradle是一個基於Apache Ant和Apache Maven概念的項目自動化構建開源工具。它使用一種基於Groovy的特定領域語言(DSL)來聲明項目設置,目前也增加了基於Kotlin語言的kotlin-based DSL,拋棄了基於XML的各種繁瑣配置。
gradle是一個構建工具,也是一個編程框架
gradle組成:
- groovy核心語法
- build script block
- gradle api
gradle的優勢:
- 相比於maven,ant構建更加靈活
- 可以服用各種插件
- 兼容上,兼容maven等所有功能
- 細粒度,可以自定義編譯過程
gradle的生命週期
gradle的生命週期主要分爲3個階段:Initialization初始化階段,Configuration配置階段,Execution執行階段
- Initialization:解析真個工程中的所有Project,構建所有Project賭贏的project對象
- Configuration:解析project中的所有的task,構建成一個有向無環圖
- Execution:執行具體的task以及其依賴的task
setting.gradle:這個文件是在初始化階段執行,一個gradle項目中必須由setting.gradle這個文件,因爲它決定了那些項目參與構建。在gradle的構建中這是最先執行的一個文件。
如何監聽gradle的生命週期呢
在setting.gradle文件中可以監聽初始化階段和配置之前的階段
gradle.settingsEvaluated {
println('初始化階段開始執行...')
}
gradle.beforeProject {
println('配置之前調用...')
}
在build.gradle文件中,可以監聽配置之後、task的執行階段和執行完成的階段
this.afterEvaluate {
println('配置之後....')
}
gradle.taskGraph.beforeTask {
println "執行階段 before task"
}
gradle.taskGraph.afterTask {
println "執行階段 afterTask "
}
this.gradle.buildFinished {
println("執行階段完畢,構建完畢...")
}
gradle Project
官方文檔:https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project
在一個gradle項目中,怎麼算是一個Project呢,以一個Android項目爲例,跟工程算是一個Project,其內部的子module也是一個Project。準確的說,只要該文件夾下有build.gradle文件,它就是一個Project。每一個module在編譯期間都會生成一個對應的project對象,我們在build.gradle中編寫的代碼,其實都是在一個project對象內部編寫。
獲取一個項目所有的project
this.getAllprojects().eachWithIndex { Project entry, int index ->
println(entry.name)
}
獲取Project中的目錄
println(this.project.getRootDir().absolutePath)
println(this.project.getBuildDir().absolutePath)
gradle中拷貝文件,非常簡單,比如把app目錄下的proguard-rules.pro文件拷貝到根目錄下的build文件夾下面
copy {
from(file('proguard-rules.pro'))
into(getRootProject().getBuildDir())
}
file(’’)函數可以定位當前project中的某個文件。
Project的依賴:
在一個Android項目的根目錄中的build.gradle中,我們都會看到下面的一段配置:
buildscript {
repositories {
google()
jcenter()
maven{
url 'http://localhost:8081/xxxx'
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.1'
}
allprojects {
repositories {
google()
jcenter()
}
}
buildscript 就是依賴配置的核心部分,它接受一個閉包,內部可以設置很多參數,最重要的就是前面的兩個repositories和dependencies。
- repositories :配置工程的倉庫地址,按照順序從不同的倉庫中查找依賴。這裏也可以添加本地的maven倉庫地址
- dependencies :配置工程的插件依賴地址(gradle程序所依賴的第三方庫)。比如
com.android.tools.build:gradle:3.5.1
就是谷歌開發的Android相關的插件還有平時開發經常用到的tinker相關插件,butterknife相關插件都在這裏。 - allprojects 子項目的倉庫
在Project中執行外部指令,比如下面的一個複製的任務:
task('copytask'){
//保證是在執行階段
doLast{
def fromPath = this.buildDir.path + '\\outputs\\apk'
def toPath = 'D:\\data'
//外部命令腳本
def command = "xcopy ${fromPath} /s ${toPath}"
println(command)
//執行代碼塊
exec {
try {
//執行 dir命令, /c 表示 cmd窗口執行完 dir 命令後立即關掉,
// 至於 cmd 後可帶哪些參數,可在終端下 cmd /? 查看
commandLine 'cmd', '/c', command
println('copytask is success')
}catch(GradleException e){
println('copytask is failed')
}
}
}
}
上面的代碼:定義一個資源路徑、一個目標路徑和一個複製的指令。這裏使用的是windows中的複製目錄的指令xcopy,在mac和linux中需要使用對應系統的指令。然後使用commandLine來執行指令。
gradle Task
官方文檔 :https://docs.gradle.org/current/dsl/org.gradle.api.Task.html#org.gradle.api.Task
task是構建過程中的基本單元,每一個task都屬於一個project,每一個task都有一個自己的名字和一個唯一的路徑,該路徑在所有project和所有的task中都是唯一的。
Task的創建方式:
第一種直接創建:
task('helloTask'){
println('hello task')
}
第二種通過TaskContainer容器創建
this.tasks.create('helloTask2'){
println('hello task2')
}
這兩種方式創建的task沒有區別,TaskContainer可以更好的管理task,它裏面有創建task和查找task等方法。
Task的配置方式
同樣,Task的配置也有兩種方式:比如下面的兩種配置分組和描述信息。
//第一種
task helloTask(group:'hello',description:'hello task des'){
println('hello task')
}
//第二種
this.tasks.create(name:'helloTask2'){
setGroup('hello')
setDescription('hello create task')
println('hello task2')
}
不同的task設置相同的分組,方便查找,比如把我們自己自定義的task都設置到一個同樣的分組中,跟系統的區分開。
自定義Task的執行時機
當我們在studio的命令行執行前面的代碼的時候,比如執行gradlew helloTask
,我們只執行第一個task,執行結果會看到helloTask和helloTask2都會輸出。這是因爲前面的寫法都是在gradle生命週期的配置階段執行。
那如何讓一個自定義的task在gradle的執行階段執行,可以使用doFirst和doLast這兩個閉包函數。因爲系統中會有很多默認的task,比如Android中build和clean等。doFirst就是在系統task執行之前執行,doLast就是在系統的task執行之後執行。
task helloTask(group:'hello',description:'hello task des'){
//配置階段執行
println('hello task')
//系統task執行之前
doFirst{
println('hello task do First')
}
//系統task執行之後
doLast{
println('hello task do Last')
}
}
通過doFirst和doLast,我們可以做一些事情,比如統計build執行階段的時間
def buildStartTime
def buildEndTime
this.afterEvaluate { Project project ->
//afterEvaluate配置階段完成後執行
def preTask = project.tasks.getByName('preBuild')
preTask.doFirst{
buildStartTime = System.currentTimeMillis()
}
def endTask = project.tasks.getByName('build')
endTask.doLast{
buildEndTime = System.currentTimeMillis()
println("build 執行時間:${buildEndTime - buildStartTime}")
}
}
//輸出
> Task :app:build
build 執行時間:28018
Task的依賴
一個task可能會依賴一個或者多個別的task,那麼在執行的時候,它所依賴的task會先執行
靜態添加依賴,當我們明確知道需要依賴那幾個task的時候
task taskA{
doLast{
println('i am taskA')
}
}
task taskC{
doLast{
println('i am taskC')
}
}
task taskB(dependsOn:[taskA,taskC]){
doLast{
println('i am taskB')
}
}
taskB依賴了taskA和taskC,運行可以看到,taskA和taskC在taskB之前執行。
動態添加依賴,比如下面的taskB依賴所有name以lib開頭的task
task lib1 {
doLast{
println('i am lib1')
}
}
task lib2 {
doLast{
println('i am lib2')
}
}
task lib3 {
doLast{
println('i am lib3')
}
}
task taskB {
dependsOn project.tasks.findAll {
task ->
return task.name.startsWith('lib')
}
doLast{
println('i am taskB')
}
}
控制task的執行順序可以通過前面的依賴的方式,也可以通過mustRunAfter這個關鍵字來指定某個task的執行必須在誰的後面。
task taskC{
doLast{
println('i am taskC')
}
}
task taskB {
mustRunAfter taskC
doLast{
println('i am taskB')
}
}
task taskA{
mustRunAfter taskB
doLast{
println('i am taskA')
}
}
前面我們知道,通過this.afterEvaluate {}
這個閉包我們可以在配置完成之後來執行我們自己的一些操作,那如何將我們自己的task插入到系統構建的中間部位執行呢?
辦法就是:通過mustRunAfter關鍵字,讓我們的task必須執行在一個系統的task之後,然後通過dependsOn來讓另一個一個系統的task依賴我們自己的task。這樣就把我們自己的task插入到了這兩個系統的task之間了。
Task的輸入輸出
一個Task的inputs和outputs可以是一個或多個文件,可以是文件夾,還可以是Project的某個Property,甚至可以是某個閉包所定義的條件。
從proguard-rules.pro中讀取數據,寫入到destination.txt文件中
task combineFileContent {
def source = file('proguard-rules.pro')
def destination = file('destination.txt')
//inputs 屬性應該被賦值爲 一個目錄、一個或多個文件、或是一個任意屬性
inputs.file source // 將sources聲明爲該Task的inputs
//outputs 應該被賦值爲一個或多個目錄或者一個或多個文件
outputs.file destination // 將destination聲明爲outputs
doLast {
source.withReader {
reader ->
def lines = reader.readLines()
destination.withWriter { writer ->
lines.each { line ->
writer.append(line+'\r\n')
}
}
}
}
}
上面的代碼,如果我們不寫inputs和outputs也是可以完成複製的,那有什麼區別呢,區別就是如果寫了,只有當源文件改變的時候,纔會重新執行此task,如果沒寫,那麼每次都會執行此task。如此看來還是寫這個執行效率會高點。在gradle中這個稱爲增量構建。