Gradle詳解(二)——Gradle
1 Gradle概述
參考
配置環境
- 去gradle官網下載gradle程式的壓縮包,解壓到硬碟。
- 在解壓得到的目錄中找到“gradle.bat”檔案,將其所在路徑(如“E:\gradle-4.2.1\bin”)新增到Windows系統的PATH環境變數中。
基本概念
Gradle是基於Groovy的一種DSL(領域特定語言),也是一個程式設計框架,它定義了一套自己的遊戲規則。我們要玩轉Gradle,必須要遵守它定義的規則。
Project:每一個待構建的工程都叫一個Project,具體來說,一個Project的標誌就是一個build.gradle檔案,也就是說,如果一個目錄直接包含一個build.gradle檔案,則該目錄就是一個Project。
Task:一個Task表示一個邏輯上較為獨立的執行過程,比如編譯Java原始碼,拷貝檔案,打包Jar檔案等。每一個Project中都包含多個Task,比如一個Android App的構建Project可能包含:Java原始碼編譯Task、資源編譯Task、JNI編譯Task、lint檢查Task、打包生成APK的Task、簽名Task等。
Plugin:即外掛,用來向Project中新增一系列Task(任務)和Property(屬性),以完成特定的構建任務。構建Java工程有Java外掛,構建Groovy工程有Groovy外掛,構建Android App工程有Android App外掛,構建Android Library工程有Android Library外掛
多Project的Gradle專案(一)
rootproject
|----build.gradle
|----settings.gradle
|----subproject1
|----build.gradle
|----subproject2
|----build.gradle
要讓subproject1和subproject2成為rootproject的子Project,需要在rootproject目錄下建立一個名為settings.gradle的檔案,並在其中加入程式碼:
//通過include函式,將子Project的名字(其資料夾名)包含進來
include ':subproject1', ':subproject2'
settings.gradle除了可以include之外,還可以做一些初始化工作。比如:
def initEnvironment(){
//doSomething
}
//呼叫initEnvironment()函式進行初始化
initEnvironment()
//其實include也是一個函式
include ':subproject1', ':subproject2'
基本gradle命令
- 執行當前Project及其所有子Project中的名為task_name的Task:
gradle task_name
- 列出當前Project及其所有子Project:
gradle projects
- 列出當前Project及其所有子Project中的全部Task:
gradle tasks
- 列出當前Project及其所有子Project中的全部Property:
gradle properties
projects、tasks、properties其實都是Gradle系統提供的Task。以gradle projects
為例,這條命令其實就是在執行名為projects的Task。
2 Gradle詳解
Gradle物件
當我們執行gradle xxx
或其他什麼的時候,會自動建立一個Gradle物件。在整個執行過程中,只有這麼一個物件。
在settings.gradle、build.gradle中都可以直接獲取Gradle物件的引用,就像這樣:println "gradle id is " +gradle.hashCode()
Project物件
每一個build.gradle檔案就代表一個Project。每個Project中都包含一系列Property(屬性)和Task(任務)。
載入外掛
載入外掛呼叫的是Project實現的PluginAware介面中的apply函式:
//此處呼叫的是上圖中最後一個 apply 函式
apply plugin: 'com.android.library' //如果是編譯 Android Library,則載入此外掛
apply plugin: 'com.android.application' //如果是編譯 Android APP,則載入此外掛
除了載入二進位制的外掛(上面的外掛其實都是下載了對應的 jar 包,這也是通常意義上我們所理解的外掛),還可以載入一個gradle檔案。
為什麼要載入gradle檔案呢?一般而言,我們會把一些通用的函式放到一個名叫utils.gradle的檔案中。然後在各個Project中載入這個utils.gradle。這樣,再經過一些處理(後面會說),就可以在各個Project中呼叫utils.gradle中定義的函數了。
載入utils.gradle的程式碼如下:
apply from: rootProject.getRootDir().getAbsolutePath() + "/utils.gradle"
apply方法的詳細說明:
Property
Gradle物件、Project物件、Task物件都有屬性(Property),而日常使用中我們經常接觸的是Project的Property。很多Plugin都會向Project中加入Property,在使用這些Plugin時,我們通常需要對這些Property進行賦值。
Gradle在預設情況下已經為Project定義了很多Property,其中比較常用的有:
project:Project本身
name:Project的名字
path:Project的絕對路徑
description:Project的描述資訊
buildDir:Project構建結果存放目錄
version:Project的版本號
舉個例子,我們首先設定Project的version和description屬性,再定義一個Task來列印這些屬性。具體來說就是在build.gradle中加入如下程式碼:
version = 'this is the project version'
description = 'this is the project description'
task showProjectProperties << {
println version
println project.description
}
然後就可以在命令列視窗中通過gradle showProjectProperties
來執行這個Task了。
需要注意的是,在列印description時,我們使用了project.description,而不是直接使用description。原因在於Project和Task中都有description屬性,如果在Task中使用description而不加字首,那麼會被認為是Task的description屬性。
額外屬性
可以給Gradle物件、Project物件、Task物件設定額外屬性(extra property)。定義額外屬性需要使用ext關鍵字,定義好之後,後面再存取額外屬性就不需要ext關鍵字了。
以Project為例,我們為其定義一個名為prop的額外屬性,只需在build.gradle中加入如下程式碼:
ext.prop = "this is property1"
也可以通過閉包的方式:
ext {
prop = "this is property2"
}
在定義了Property之後,使用時就不需要寫ext了:
task showProperties << {
println prop
}
額外屬性有什麼用呢?舉個例子:我們在settings.gradle中為Gradle物件設定一些額外屬性:
include 'subproject1','subproject2'
//設定一個名為hello的額外屬性
gradle.ext.hello = 'hello world'
因為在每個Project中都可以獲取到Gradle物件,因此,我們完全可以把Gradle物件的額外屬性當做一個全域性變數來使用。例如我們在subproject1的build.gradle中定義了這樣一個Task:
task sayHello << {
println gradle.hello
}
在命令列執行gradle sayHello
,可以發現正確打印出了hello world。
再來一個例子強化一下。我們在utils.gradle中定義了一些函式,然後想在各個Project中呼叫這些函式。那該怎麼做呢?
//utils.gradle中定義了一個獲取AndroidManifests.xml中versionName的函式
def getVersionNameAdvanced(){
//project指的是載入此utils.gradle的Project物件
def xmlFile = project.file("AndroidManifest.xml")
def rootManifest = new XmlSlurper().parse(xmlFile)
return rootManifest['@android:versionName']
}
//我們可以把getVersionNameAdvanced函式賦值給一個外部屬性
//然後在想要呼叫getVersionNameAdvanced函式的Project中apply此utils.gradle
//此ext是誰的ext?——是載入此utils.gradle的Project物件的ext
ext{
//除了 ext.xxx=value 這種寫法之外,還可以使用 ext{} 這種寫法
getVersionNameAdvanced = this.&getVersionNameAdvanced
}
Task
定義Task
我們可以通過多種方式為Project定義Task,所有的Task都會被存放在Project的TaskContainer中。
(1)呼叫Project的task()方法建立Task
這是建立Task最常見的方式:
task hello1 << {
println 'hello1'
}
這裡的“<<”表示追加的意思,即向hello1中加入執行過程。我們還可以使用doLast來達到同樣的效果:
task hello2 {
doLast {
println 'hello2'
}
}
另外,如果需要向Task的最前面加入執行過程,我們可以使用doFirst:
task hello3 {
doFirst {
println 'hello3'
}
}
以上我們自定義的3個Task都位於TaskContainer中,Project中的tasks屬性即表示該TaskContainer。
(2)通過TaskContainer的create()方法建立Task
tasks.create(name: 'hello4') << {
println 'hello4'
}
(3)建立Task時指定Task的型別
在建立Task時,我們可以宣告該Task的型別。如果不宣告其型別,那麼它的型別就是DefaultTask。
task copyFile(type: Copy) {
from 'xml'
into 'destination'
}
以上copyFile將xml資料夾中的所有內容拷貝到destination資料夾中,其中xml資料夾與destination資料夾與當前Project(即build.gradle檔案)位於同一目錄下。
注意:對於我們建立的每一個Task,Gradle都會在Project中建立一個同名的Property,所以我們可以將該Task當作Property來訪問。
宣告Task間的依賴關係
Task之間可以存在依賴關係。如果TaskA依賴TaskB,那麼在執行TaskA時,Gradle會先執行TaskB,再執行TaskA。
可以在定義一個Task的同時宣告它的依賴關係:
task hello5(dependsOn:hello4) << {
println 'hello5'
}
也可以在定義Task之後再單獨宣告依賴:
task hello6 << {
println 'hello6'
}
hello6.dependsOn hello5
配置Task
一個Task除了執行操作之外,還可以包含多個Property,其中有Gradle為每個Task預設定義的Property,比如description,logger等。另外,每一個Task型別通常還含有自己的特有Property,比如Copy的from和to等。此外,我們還可以動態地向Task中新增額外Property。
在執行一個Task之前,我們通常需要先設定其Property值。Gradle提供了多種方法設定Task的Property值。
(1)在定義Task的時候配置其Property:
task hello7 << {
description = "this is hello7"
println description
}
(2)通過同名屬性來配置Task的Property:
前面已經說過,當我們建立一個Task的時候,Gradle會自動在Project中建立一個同名的Property。
hello7.description = "this is hello7"
(3)通過閉包的方式來配置一個已有的Task:
當我們建立一個Task的時候,Gradle除了會建立一個同名的Property,還會建立一個同名的方法,我們可以通過呼叫這個方法並傳入一個閉包來對Task的Property進行配置:
task hello8 << {
println description
}
hello8 {
description = "this is hello8"
}
需要注意的是,Gradle在執行Task時分為兩個階段,首先是配置階段,然後才是實際執行階段。也就是說在執行hello8之前,Gradle會掃描整個build.gradle文件,將hello8的description設定為“this is hello8”,然後執行hello8。
(4)通過Task的configure()方法設定Property:
task hello9 << {
println description
}
hello9.configure {
description = "this is hello9"
}
多Project的Gradle專案(二)
對於上面所提到的多Project的Gradle專案:
rootproject
|----build.gradle
|----settings.gradle
|----subproject1
|----build.gradle
|----subproject2
|----build.gradle
如果我們想要定義Task來顯示每個Project各自的名稱。我們可以在每個build.gradle中進行定義,但這是一種比較笨的方法,此時我們也完全沒有享受到Gradle的多Project構建功能所帶來的好處。在Gradle中,我們可以通過根Project的allprojects()方法將配置一次性地應用於所有的Project,當然也包括定義Task。比如,在rootproject的build.gradle中,我們可以做以下定義:
allprojects {
task getnames << {
println project.name
}
}
在rootproject目錄下執行gradle getnames
,輸出如下:
> Task :getnames
rootproject
> Task :subproject1:getnames
subproject1
> Task :subproject2:getnames
subproject2
可見rootproject、subproject1、subproject2中都有了一個名為getnames的Task。
除了allprojects()之外,Project還提供了subprojects()方法用於配置所有的子Project(不包含根Project)。
一旦有了多個Project,他們之間便會存在著依賴關係。Gradle的Project之間的依賴關係是基於Task的,而不是整個Project的。
現在,讓我們來看一個Project依賴的例子。比如subproject1中有taskA和taskB,在subproject2中有taskC和taskD,taskA依賴taskB、taskC,taskB依賴taskD:
//subproject1中的build.gradle
task taskA << {
println 'this is taskA from project 1'
}
task taskB << {
println 'this is taskB from project 1'
}
taskA.dependsOn taskB
taskA.dependsOn ':subproject2:taskC'
taskB.dependsOn ':subproject2:taskD'
//subproject2中的build.gradle
task taskC << {
println 'this is taskC from project 2'
}
task taskD << {
println 'this is taskD from project 2'
}
此時執行gradle taskA
,輸出如下:
:subproject2:taskD
this is taskD from project 2
:subproject1:taskB
this is taskB from project 1
:subproject2:taskC
this is taskC from project 2
:subproject1:taskA
this is taskA from project 1
Gradle工作流程
當我們執行一個gradle xxx
命令時,會經歷以下階段:
3 Gradle高階
自定義Task型別
我們以定義一個簡單的HelloWorldTask為例,講解如何自定義一個Task型別,並且如何對其進行配置。
(一)在build.gradle檔案中直接定義
我們知道,Gradle其實就是groovy程式碼,所以在build.gradle檔案中,我們便可以定義Task類:
//定義HelloWorldTask
class HelloWorldTask extends DefaultTask {
@Optional
String message = 'I am davenkin'
@TaskAction
def hello(){
println "hello world $message"
}
}
//使用HelloWorldTask型別來定義Task
task hello(type:HelloWorldTask)
task hello1(type:HelloWorldTask){
message ="I am a programmer"
}
在上例中,我們定義了一個名為HelloWorldTask的Task,它需要繼承自DefaultTask,它的作用是向命令列輸出一個字串。@TaskAction表示該Task要執行的動作,即在呼叫該Task時,hello()方法將被執行。另外,message被標記為@Optional,表示在配置該Task時,message是可選的。在定義好HelloWorldTask後,我們建立了兩個Task例項,第一個hello使用了預設的message值,而第二個hello1在建立時重新設定了message的值。
執行hello時,輸出hello world I am davenkin
;執行hello1時,輸出hello world I am a programmer
。
(二)在當前工程中定義Task型別
(三)在單獨的專案中定義Task型別
自定義外掛
在Plugin中,我們可以向Project中加入新的Task和Property等。
舉個例子,建立一個DateAndTimePlugin,該Plugin定義了2個Task,分別用於輸出系統當前的日期和時間,另外,我們可以配置日期和時間的輸出格式。
(一)在build.gradle檔案中直接定義Plugin
和在build.gradle檔案中定義Task型別一樣,我們可以將對Plugin的定義直接寫在build.gradle中:
定義外掛:
class DateAndTimePlugin implements Plugin<Project> {
void apply(Project project) {
//向project新增extension
project.extensions.create("dateAndTime", DateAndTimePluginExtension)
//向project新增Task
project.task('showTime') << {
println "Current time is " + new Date().format(project.dateAndTime.timeFormat)
}
//向project新增Task
project.tasks.create('showDate') << {
println "Current date is " + new Date().format(project.dateAndTime.dateFormat)
}
}
}
class DateAndTimePluginExtension {
String timeFormat = "MM/dd/yyyyHH:mm:ss.SSS"
String dateFormat = "yyyyMMdd"
}
使用外掛並進行配置:
apply plugin: DateAndTimePlugin
dateAndTime {
timeFormat = 'HH:mm:ss.SSS'
dateFormat = 'MM/dd/yyyy'
}
然後就可以使用showTime和showDate這兩個Task了。
每一個自定義的Plugin都需要實現Plugin介面。該介面定義了一個apply()方法,在該方法中,我們可以操作Project,比如向其中加入Task,定義額外的Property等。
在上例中,我們在DateAndTimePlugin中向Project添加了2個Task,一個名為showTime,一個名為showDate。
每個Gradle的Project都維護了一個ExtenionContainer,我們可以通過project.extentions進行訪問,比如讀取額外的Property和定義額外的Property等。在DateAndTimePlugin中,我們向Project中定義了一個名為dateAndTime的extension,並向其中加入了2個Property,分別為timeFormat和dateFormat。它們分別用於showTime和showDate。在使用該Plugin時,我們可以通過以下方式對這兩個Property進行重新配置:
dateAndTime {
timeFormat = 'HH:mm:ss.SSS'
dateFormat = 'MM/dd/yyyy'
}
(二)在當前工程中定義Plugin
(三)在單獨的專案中建立Plugin