1. 程式人生 > >Gradle詳解(二)——Gradle

Gradle詳解(二)——Gradle

1 Gradle概述

參考

配置環境

  1. 去gradle官網下載gradle程式的壓縮包,解壓到硬碟。
  2. 在解壓得到的目錄中找到“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專案(一)

root­project
    |----build.gradle
    |----settings.gradle
    |----sub­project1
              |----build.gradle
    |----sub­project2
              |----build.gradle

要讓sub­project1和sub­project2成為root­project的子Project,需要在root­project目錄下建立一個名為settings.gradle的檔案,並在其中加入程式碼:

//通過include函式,將子Project的名字(其資料夾名)包含進來
include ':sub­project1', ':sub­project2'

settings.gradle除了可以include之外,還可以做一些初始化工作。比如:

def initEnvironment(){
    //doSomething
}

//呼叫initEnvironment()函式進行初始化
initEnvironment()

//其實include也是一個函式
include ':sub­project1', ':sub­project2'

基本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,其中比較常用的有:

projectProject本身
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專案:

root­project
    |----build.gradle
    |----settings.gradle
    |----sub­project1
              |----build.gradle
    |----sub­project2
              |----build.gradle

如果我們想要定義Task來顯示每個Project各自的名稱。我們可以在每個build.gradle中進行定義,但這是一種比較笨的方法,此時我們也完全沒有享受到Gradle的多Project構建功能所帶來的好處。在Gradle中,我們可以通過根Project的allprojects()方法將配置一次性地應用於所有的Project,當然也包括定義Task。比如,在root­project的build.gradle中,我們可以做以下定義:

allprojects {
    task getnames << {
        println project.name
    }
}

在root­project目錄下執行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依賴的例子。比如sub­project1中有taskA和taskB,在sub­project2中有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 ':sub­project2:taskC'
taskB.dependsOn ':sub­project2:taskD'
//subproject2中的build.gradle

task taskC << {
    println 'this is taskC from project 2'
} 

task taskD << {
    println 'this is taskD from project 2'
}

此時執行gradle taskA,輸出如下:

:sub­project2:taskD
this is taskD from project 2
:sub­project1:taskB
this is taskB from project 1
:sub­project2:taskC
this is taskC from project 2
:sub­project1: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 = "yyyy­MM­dd"
}

使用外掛並進行配置:

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