gradle深入研究

Gradle

1.基本元素

Project

每個(gè)項(xiàng)目的編譯至少有一個(gè) Project,一個(gè) build.gradle就代表一個(gè)project,每個(gè)project里面包含了多個(gè)task,task里面又包含很多action,action是一個(gè)代碼塊脐区,里面包含了需要被執(zhí)行的代碼愈诚。

Script

gradle的腳本文件,通過腳本牛隅,我們可以定義一個(gè)Project

Task

Project中的具體執(zhí)行的原子性工作炕柔,以構(gòu)建一個(gè)工程為例,它可以是 編譯,執(zhí)行單元測(cè)試,發(fā)布 等媒佣。

2.Script元素

Init Script

似乎從來沒有使用過匕累,但是在每一次構(gòu)建開始之前,都會(huì)執(zhí)行init script丈攒,用來設(shè)置一些全局變量哩罪,有多個(gè)位置可以存放init script如下:

USER_HOME/.gradle/
USER_HOME/.gradle/init.d/
GRADLE_HOME/init.d/
Settings Script

用來在組織多工程的構(gòu)建距帅,存在于root工程下片排,settings.gradle,用于生命該工程都包含哪些project

上述script在運(yùn)行時(shí)都會(huì)被編譯成一個(gè)實(shí)現(xiàn)了Script接口的class谐腰,同時(shí)每一個(gè)script都有一個(gè)委托對(duì)象

Build Script -> Project

Init Script  -> Gradle

Settings Script -> Settings
Build Script

每一個(gè)build.gradle都是一個(gè)Build Scrpit,它由兩種元素組成显设。

statement

可以包含方法調(diào)用框弛,屬性賦值,局部變量定義等.

script blocks

block的概念稍微復(fù)雜一點(diǎn)捕捂,首先我們先要理解一個(gè)groovy的元素瑟枫,閉包.

有了閉包的概念,那么理解script block就沒有障礙了指攒,直接看文檔中的定義:

A script block is a method call which takes a closure as a parameter. The closure is treated as a configuration closure which configures some delegate object as it executes.

翻譯一下就是

一個(gè)腳本塊是一個(gè)接受一個(gè)閉包作為參數(shù)的方法慷妙,這個(gè)閉包在執(zhí)行的時(shí)候配置它的委托對(duì)象。

舉個(gè)例子??

def buildVersion = '1.2.0'
def author = 'liuboyu'
allprojects {
    repositories {
        jcenter()
    }
    setVersion(buildVersion)
    println "this project_${name}_${getVersion()} is created by ${author}"
}

首先我們定義了兩個(gè)變量分別是buildVersion和author,在執(zhí)行時(shí)這個(gè)兩個(gè)變量會(huì)成為Script Class的屬性允悦。然后膝擂,我們使用了一個(gè)script block,根據(jù)定義,這個(gè)block對(duì)應(yīng)著一個(gè)同名方法allprojects架馋,可是我們并沒有在腳本中定義這樣一個(gè)方法狞山,那它如何執(zhí)行呢?回想一下我們剛剛看到的build script的委托對(duì)象叉寂,沒錯(cuò)萍启,這個(gè)方法被委托給了Project對(duì)象執(zhí)行,查看文檔屏鳍,我們確實(shí)在Project中找到了這個(gè)同名方法.

接下來勘纯,我們?cè)趬K中寫了兩行代碼,這就是這個(gè)閉包需要執(zhí)行的代碼,首先打印一行文字孕蝉,其次setVersion()屡律。同樣的腌逢,我們沒有定義setVersion這個(gè)方法降淮,這就涉及到閉包的一些概念,我們換一種寫法

def buildVersion = '1.2.0'
def author = 'liuboyu'
allprojects {
    repositories {
        jcenter()
    }
    delegate.setVersion(buildVersion)
    println "this project_${delegate.name}_${delegate.getVersion()} is created by ${author}"
}

setVersion 這個(gè)方法實(shí)際上是由閉包的委托對(duì)象執(zhí)行的搏讶,那委托對(duì)象是什么呢佳鳖?我們查閱一下allprojects這個(gè)方法的Api,如[api文檔](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:allprojects(groovy.lang.Closure)
[圖片上傳失敗...(image-76ac25-1537328769306)]
這個(gè)閉包的委托對(duì)象是當(dāng)前的project和它的子project,也就是對(duì)于一個(gè)包含子工程的工程媒惕,這個(gè)閉包會(huì)執(zhí)行多次,我們實(shí)驗(yàn)一下

this project_GradleDeepTest_1.2.0 is created by liuboyu
this project_app_1.2.0 is created by liuboyu
this project_testlibrary_1.2.0 is created by liuboyu

閉包中的 Owner,delegate,this

閉包內(nèi)部通常會(huì)定義一下3種類型:

  • this corresponds to the enclosing class where the closure is defined
  • this 對(duì)應(yīng)于閉包定義處的封閉類
  • owner corresponds to the enclosing object where the closure is defined, which may be either a class or a closure
  • owner 對(duì)應(yīng)于閉包定義處的封閉對(duì)象(可能是一個(gè)類或者閉包)
  • delegate corresponds to a third party object where methods calls or properties are resolved whenever the receiver of the message is not defined
  • delegate 對(duì)應(yīng)于方法調(diào)用或?qū)傩蕴幍牡谌綄?duì)象,無論消息接收者是否定義系吩。
this

在閉包中,調(diào)用getThisObject將會(huì)返回閉包定義處所處的類。等價(jià)于使用顯示的this:

class Enclosing {
    void run() {
        // 定義在Enclosing類中的閉包,并且返回getThisObject
        def whatIsThisObject = { getThisObject() }

        // 調(diào)用閉包將會(huì)返回一個(gè) 閉包定義處的類的Enclosing的實(shí)例
        assert whatIsThisObject() == this

        // 可以使用簡潔的this符號(hào)
        def whatIsThis = { this }

        // 返回同一個(gè)對(duì)象
        assert whatIsThis() == this
        println("Enclosing success " + this)
    }
}
class EnclosedInInnerClass {
    class Inner {
        // 內(nèi)部類中的閉包
        Closure cl = { this }
    }
    void run() {
        def inner = new Inner()
        // 在內(nèi)部類中的this將會(huì)返回內(nèi)部類,而不是頂層的那個(gè)類妒蔚。
        assert inner.cl() == inner
        println("EnclosedInInnerClass success")
    }
}
class NestedClosures {
    void run() {
        def nestedClosures = {
            // 閉包內(nèi)定義閉包
            def cl = { this }
            cl()
        }
        // this對(duì)應(yīng)于最近的外部類,而不是封閉的閉包!
        assert nestedClosures() == this
    }
}
class Person {
    String name
    int age
    String toString() { "$name is $age years old" }

    String dump() {
        def cl = {
            String msg = this.toString()//在閉包中使用this調(diào)用toString方法,將會(huì)調(diào)用閉包所在封閉類對(duì)象的toString方法,也就是Person的實(shí)例
            println msg
        }
        cl()
    }
}
def p = new Person(name:'Janice', age:74)
assert p.dump() == 'Janice is 74 years old'
Owner

閉包中的owner和閉包中的this的定義非常的像,只不過有一點(diǎn)微妙的不同:它將返回它最直接的封閉的對(duì)象,可以是一個(gè)閉包也可以是一個(gè)類的:

class Enclosing2 {
    void run() {
        // 定義在Enclosing類中的閉包,getOwner
        def whatIsThisObject = { getOwner() }

        // 調(diào)用閉包將會(huì)返回一個(gè) 閉包定義處的類的Enclosing的實(shí)例
        assert whatIsThisObject() == this

        // 使用簡潔的owner符號(hào)
        def whatIsThis = { owner }

        // 返回同一個(gè)對(duì)象
        assert whatIsThis() == this
        println("Enclosing2 success " + this)
    }
}
class EnclosedInInnerClass2 {
    class Inner {
        // 內(nèi)部類中的閉包
        Closure cl = { owner }
    }
    void run() {
        def inner = new Inner()
        // 在內(nèi)部類中的owner將會(huì)返回內(nèi)部類,而不是頂層的那個(gè)類穿挨。
        assert inner.cl() == inner
        println("EnclosedInInnerClass success")
    }
}
class NestedClosures2 {
    void run() {
        def nestedClosures = {
            // 閉包內(nèi)定義閉包
            def cl = { owner }
            cl()
        }
        // owner對(duì)應(yīng)的是封閉的閉包,這是不同于this的地方
        assert nestedClosures() == nestedClosures
    }
}
Delegate

對(duì)于delegate來講,它的含義大多數(shù)情況下是跟owner的含義一樣肴盏,除非它被顯示的修改(通過Closure.setDelegate()方法進(jìn)行修改)科盛。

class Enclosing3 {
    void run() {
        // 獲得閉包的delegate可以通過調(diào)用getDelegate方法
        def cl = { getDelegate() }

        // 使用delegate屬性
        def cl2 = { delegate }

        // 二者返回同樣的對(duì)象
        assert cl() == cl2()

        // 是封閉的類或這閉包
        assert cl() == this

        // 特別是在閉包的內(nèi)部的閉包
        def closure = {
            // 閉包內(nèi)定義閉包
            def cl3 = { delegate }
            cl3()
        }

        // delegate對(duì)應(yīng)于owner返回同樣的對(duì)象或者閉包
        assert closure() == closure
    }
}
def scriptClosure={
            println "scriptClosure this:"+this
            println "scriptClosure owner:"+owner
            println "scriptClosure delegate:"+delegate
        }
        println "before setDelegate()"
        scriptClosure.call()
        scriptClosure.setDelegate ("abc")
        println "after setDelegate()"
        scriptClosure.call()

結(jié)果:

before setDelegate()
scriptClosure this:class Client2
scriptClosure owner:class Client2
scriptClosure delegate:class Client2
after setDelegate()
scriptClosure this:class Client2
scriptClosure owner:class Client2
scriptClosure delegate:abc

閉包的delegate可以被更改為任意的對(duì)象。先定義兩個(gè)相互之間沒有繼承關(guān)系的類,二者都定義了一個(gè)名為name的屬性:

class Person {
    String name
    def upperCasedName = { delegate.name.toUpperCase() }
}

def p1 = new Person(name:'Janice', age:74)
def p2 = new Person(name:'liuboYu', age:18)

然后,定義一個(gè)閉包通過delegate獲取一下name屬性:

p1.upperCasedName.delegate = p2
println(p1.upperCasedName())

然后,通過改變閉包的delegate,你可以看到目標(biāo)對(duì)象發(fā)生了改變:

JANICE
LIUBOYU

委托機(jī)制

無論何時(shí),在閉包中,訪問一個(gè)屬性,不需要指定接收對(duì)象,這時(shí)使用的是delegation strategy:

class Person {
    String name
}
def person = new Person(name:'Igor')
def cl = { name.toUpperCase() } //name不是閉包括號(hào)內(nèi)的一個(gè)變量的索引
cl.delegate = person //改變閉包的delegate為Person的實(shí)例
assert cl() == 'IGOR'//調(diào)用成功

之所以可以這樣調(diào)用的原因是name屬性將會(huì)自然而然的被delegate的對(duì)象征用菜皂。這樣很好的解決了閉包內(nèi)部屬性或者方法的調(diào)用贞绵。不需要顯示的設(shè)置(delegate.)作為接收者:調(diào)用成功是因?yàn)槟J(rèn)的閉包的delegation strategy使然。閉包提供了多種策略方案你可以選擇:

  • Closure.OWNER_FIRST 是默認(rèn)的策略恍飘。如果一個(gè)方法存在于owner,然后他將會(huì)被owner調(diào)用榨崩。如果不是,然后delegate將會(huì)被使用
  • Closure.Delegate_FIRST 使用這樣的邏輯:delegate首先使用,其次是owner
  • Closure.OWNER_ONLY 只會(huì)使用owner:delegate會(huì)被忽略
  • Closure.DELEGATE_ONLY 只用delegate:忽略owner
  • Closure.TO_SELF can be used by developers who need advanced meta-programming techniques and wish to implement a custom resolution strategy: the resolution will not be made on the owner or the delegate but only on the closure class itself. It makes only sense to use this if you implement your own subclass of Closure.

使用下面的代碼來描繪一下”owner first”

class Person {
    String name
    def pretty = { "My name is $name" }  //定義一個(gè)執(zhí)行name的閉包成員
    String toString() {
        pretty()
    }
}
class Thing {
    String name //類和Person和Thing都定義了一個(gè)name屬性
}

def p = new Person(name: 'Sarah')
def t = new Thing(name: 'Teapot')

assert p.toString() == 'My name is Sarah'//使用默認(rèn)的機(jī)制,name屬性首先被owner調(diào)用
p.pretty.delegate = t  //設(shè)置delegate為Thing的實(shí)例對(duì)象t
assert p.toString() == 'My name is Sarah'//結(jié)果沒有改變:name被閉包的owner調(diào)用

然而,改變closure的解決方案的策略改變結(jié)果是可以的:

p.pretty.resolveStrategy = Closure.DELEGATE_FIRST
assert p.toString() == 'My name is Teapot'

通過改變r(jià)esolveStrategy,我們可以改變Groovy”顯式this”的指向:在這種情況下,name將會(huì)首先在delegate中找到,如果沒有發(fā)現(xiàn)則是在owner中尋找。name被定義在delegate中,Thing的實(shí)例將會(huì)被使用章母。

“delegate first”和”delegate only”或者”owner first”和”owner only”之間的區(qū)別可以被下面的這個(gè)其中一個(gè)delegate沒有某個(gè)屬性/方法的例子來描述:

class Person {
    String name
    int age
    def fetchAge = { age }
}
class Thing {
    String name
}
def p = new Person(name:'Jessica', age:42)
        def t = new Things(name:'Printer')
        def cl = p.fetchAge
        cl.delegate = p
        assert cl() == 42
        cl.delegate = t
        assert cl() == 42
        cl.resolveStrategy = Closure.DELEGATE_ONLY
        cl.delegate = p
        assert cl() == 42
        cl.delegate = t
        try {
            cl()
        } catch (MissingPropertyException ex) {
            println(" \"age\" is not defined on the delegate")
        }

在這個(gè)例子中,我們定義了兩個(gè)都有name屬性的類,但只有Person具有age屬性母蛛。Person類同時(shí)聲明了一個(gè)指向age的閉包。我們改變默認(rèn)的方案策略,從”owner first”到”delegate only”乳怎。由于閉包的owner是Person類,如果delegate是Person的實(shí)例,將會(huì)成功調(diào)用這個(gè)閉包,但是如果我們調(diào)用它,且它的delegate是Thing的實(shí)例,將會(huì)調(diào)用失敗,并拋出groovy.lang.MissingPropertyException彩郊。盡管這個(gè)閉包定義在Person類中,但owner沒有被使用。

image

subprojects、dependencies焦辅、repositories 都是 script blocks,后面都需要跟一個(gè)花括號(hào),通過查閱文檔可以發(fā)現(xiàn)博杖,其實(shí)就是個(gè)閉包。
我們通過源碼可以查看

/**
   * <p>Configures the build script classpath for this project.
   *
   * <p>The given closure is executed against this project's {@link ScriptHandler}. The {@link ScriptHandler} is
   * passed to the closure as the closure's delegate.
   *
   * @param configureClosure the closure to use to configure the build script classpath.
   */
  void buildscript(@DelegatesTo(value = ScriptHandler.class, strategy = Closure.DELEGATE_FIRST) @ClosureParams(value = SimpleType.class, options = {"org.gradle.api.initialization.dsl.ScriptHandler"}) Closure configureClosure);

它的 closure 是在一個(gè)類型為 ScriptHandler 的對(duì)象上執(zhí)行的筷登。主意用來所依賴的 classpath 等信息剃根。通過查看 ScriptHandler API 可知,在 buildscript SB 中前方,你可以調(diào)用 ScriptHandler 提供的 repositories(Closure )狈醉、dependencies(Closure)函數(shù)。這也是為什么 repositories 和 dependencies 兩個(gè) script blocks 為什么要放在 buildscript 的花括號(hào)中的原因惠险。

repositories 表示代碼倉庫的下載來源苗傅,默認(rèn)的來源是jcenter。

Gradle支持的代碼倉庫有幾種類型:

  1. Maven中央倉庫班巩,不支持https訪問渣慕,聲明方法為mavenCentral()
  2. JCenter中央倉庫,實(shí)際上也是用Maven搭建抱慌,通過CDN分發(fā)逊桦,并且支持https訪問,也就是我們上面默認(rèn)的聲明方法:jcenter
  3. Maven本地倉庫抑进,可以通過本地配置文件配置强经,通過USER_HOME/.m2/下的settings.xml配置文件修改默認(rèn)路徑位置,聲明方法為mavenLocal()
  4. 常規(guī)的第三方maven庫寺渗,需要設(shè)置訪問url匿情,聲明方法為maven,這個(gè)一般是有自己的maven私服
  5. Ivy倉庫信殊,可以是本地倉庫炬称,也可以是遠(yuǎn)程倉庫
  6. 直接使用本地文件夾作為倉庫

dependencies 表明項(xiàng)目依賴對(duì)應(yīng)版本的Gradle構(gòu)建工具,但更加具體的版本信息卻是在gradle-wrapper.properties這個(gè)文件中鸡号,具體如:

#Tue Oct 31 15:31:02 CST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip

allprojects 指定所有參與構(gòu)建的項(xiàng)目使用的倉庫的來源转砖。

這里我們有一個(gè)疑問:buildscript和allprojects都指定使用的倉庫的來源,它們的真正區(qū)別在哪里呢鲸伴?

  1. buildScript塊的repositories主要是為了Gradle腳本自身的執(zhí)行府蔗,獲取腳本依賴插件。
  2. 根級(jí)別的repositories主要是為了當(dāng)前項(xiàng)目提供所需依賴包汞窗,但在Android中姓赤,這個(gè)跟allprojects的repositories的作用是一樣的。同樣dependencies也可以是根級(jí)別的仲吏。
  3. allprojects塊的repositories用于多項(xiàng)目構(gòu)建不铆,為所有項(xiàng)目提供共同所需依賴包蝌焚。而子項(xiàng)目可以配置自己的repositories以獲取自己獨(dú)需的依賴包。

實(shí)際上誓斥,allprojects是用于多項(xiàng)目構(gòu)建只洒,在Android中,使用多項(xiàng)目構(gòu)建劳坑,其實(shí)就是多Module構(gòu)建毕谴。
我們看settings.gradle這個(gè)文件:

include ':app', ':testlibrary'

通過include將app這個(gè)module添加進(jìn)來。從根目錄開始距芬,一直到include進(jìn)來的所有module涝开,都會(huì)執(zhí)行allprojects的內(nèi)容。
我們也可以通過subprojects來指定module和根目錄不同的行為框仔。
例如:

subprojects {
    println " ---${delegate.name}---subprojects------ "
}

上面的例子舀武,只有子project會(huì)執(zhí)行,root project并不會(huì)執(zhí)行
或者我們也可以單獨(dú)指定某個(gè)project自己的行為离斩。
我們是否可以指定某個(gè)module執(zhí)行它特有的行為呢银舱?
例如:

project(':testlibrary'){
    println " ---testlibrary---subprojects------ "
}

project(':app'){
    println " ---app---subprojects------ "
}

Android自己也定義了好多ScriptBlock,請(qǐng)參考DSL參考文檔

image

下圖為 buildToolsVersion 和 compileSdkVersion 的說明:

image

接下來看一下

defaultConfig {
        applicationId "test.project.com.gradledeeptest"
        minSdkVersion 15
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
    }

[圖片上傳失敗...(image-1b57f5-1537328769307)]

3.Gradle 組成

Gradle 主要有三種對(duì)象捐腿,這三種對(duì)象和三種不同的腳本文件對(duì)應(yīng)纵朋,在 gradle 執(zhí)行的時(shí)候,會(huì)將腳本轉(zhuǎn)換成對(duì)應(yīng)的對(duì)端:

  • Gradle 對(duì)象:當(dāng)我們執(zhí)行 gradle xxx 或者什么的時(shí)候茄袖,gradle 會(huì)從默認(rèn)的配置腳本中構(gòu)造出一個(gè) Gradle 對(duì)象。在整個(gè)執(zhí)行過程中嘁锯,只有這么一個(gè)對(duì)象宪祥。Gradle 對(duì)象的數(shù)據(jù)類型就是 Gradle。我們一般很少去定制這個(gè)默認(rèn)的配置腳本家乘。
  • Project 對(duì)象:每一個(gè) build.gradle 會(huì)轉(zhuǎn)換成一個(gè) Project 對(duì)象蝗羊。
  • Settings 對(duì)象:顯然,每一個(gè) settings.gradle 都會(huì)轉(zhuǎn)換成一個(gè) Settings 對(duì)象仁锯。

4.Gradle 對(duì)象

[圖片上傳失敗...(image-2f9871-1537328769307)]
我們寫個(gè)例子耀找,驗(yàn)證只有全局只有一個(gè)gradle,build.gradle 中和 settings.gradle 中分別加了如下輸出:

println "setting In posdevice, gradle id is " + gradle.hashCode()
println "setting version: " + gradle.gradleVersion

得到結(jié)果如下所示:

setting In posdevice, gradle id is 1466991549
setting version: 4.1
library In posdevice, gradle id is 1466991549
library version: 4.1
app In posdevice, gradle id is 1466991549
app version: 4.1
  • 在 settings.gradle 和 posdevice build.gradle 中业崖,我們得到的 gradle 實(shí)例對(duì)象的 hashCode 是一樣的(都是 791279786)
  • gradleVersion輸出當(dāng)前 gradle 的版本

5.Project 對(duì)象

每一個(gè) build.gradle 文件都會(huì)轉(zhuǎn)換成一個(gè) Project 對(duì)象野芒。在 Gradle 術(shù)語中,Project 對(duì)象對(duì)應(yīng)的是 Build Script双炕。

Project 包含若干 Tasks狞悲。另外,由于 Project 對(duì)應(yīng)具體的工程妇斤,所以需要為 Project 加載所需要的插件摇锋,比如為 Android 工程加載 android 插件丹拯。一個(gè) Project 包含多少 Task 往往是插件決定的。

  • 創(chuàng)建一個(gè)Settings對(duì)象荸恕,
  • 根據(jù)settings.gradle文件配置它
  • 根據(jù)Settings對(duì)象中定義的工程的父子關(guān)系創(chuàng)建Project對(duì)象
  • 執(zhí)行每一個(gè)工程的build.gradle文件配置上一步中創(chuàng)建的Project對(duì)
加載插件
image
apply plugin: 'com.android.library' <==如果是編譯 Library乖酬,則加載此插件
apply plugin: 'com.android.application' <==如果是編譯 Android APP,則加載此插件

除了加載二進(jìn)制的插件(上面的插件其實(shí)都是下載了對(duì)應(yīng)的 jar 包融求,這也是通常意義上我們所理解的插件)剑刑,還可以加載一個(gè) gradle 文件

apply from: "utils.gradle"

6.全局變量 ext

我們前面講解了gradle的生命周期,在配置的過程中双肤,整個(gè)項(xiàng)目會(huì)生成一個(gè)gradle 對(duì)象施掏,每個(gè)build.gradle的文檔都會(huì)生成一個(gè)project對(duì)象。這兩個(gè)對(duì)象都有一個(gè)ext茅糜,這個(gè)ext的屬性就類似于我們的錢包一樣七芭,獨(dú)立屬于gradle與project對(duì)象。我們可以往這個(gè)ext對(duì)象里面放置屬性蔑赘。

6.1 gradle 的ext對(duì)象

我們可以使用這樣的方法存儲(chǔ)一個(gè)變量狸驳,這個(gè)變量屬于gradle,整個(gè)工程都能使用

    //第一次定義或者設(shè)置它的時(shí)候需要 ext 前綴
    gradle.ext.api = properties.getProperty('sdk.api')

    println gradle.api  //再次存取 api 的時(shí)候缩赛,就不需要 ext 前綴了

或者直接在 gradle.properties 文件中添加屬性耙箍,作用域也是全局的
讀取方式如下

task A{
    doLast{
         println(gradle.api)
    }
}
6.2 project 的ext對(duì)象

保存值

ext{
     myName ='sohu'
     age = 18
}

獲取值,可以直接獲取

println(myName)

上面這個(gè)代碼 println(myName) 就等于println (project.ext.myName)

我們一般在ext內(nèi)存儲(chǔ)一些通用的變量酥馍,除此以外辩昆,我們也使用這個(gè)ext來做一些很酷的功能,比如說我們的gradle文件很大了旨袒,我們可以好像代碼一下汁针,進(jìn)行抽取。
project 的ext對(duì)象 的作用域是當(dāng)前的project

7.生命周期

1212336-5ab3ae4bc237b401.png
初始化階段:

主要是解析 setting.gradle 文件砚尽,gradle支持單工程和多工程構(gòu)建施无,在初始化的過程中,gradle決定了這次構(gòu)建包含哪些工程必孤,并且為每一個(gè)工程創(chuàng)建一個(gè)Project對(duì)象猾骡。并且,所有在Settings script中包含的工程的build script都會(huì)執(zhí)行敷搪,因?yàn)間radle需要為每一個(gè)Project對(duì)象配置完整的信息兴想。

讀取配置階段:

主要是解析所有的 projects 下的 build.gradle 文件,在配置的過程中购啄,本次構(gòu)建包含的所有工程的build script 都會(huì)執(zhí)行一次襟企,同時(shí)每個(gè)工程的Project對(duì)象都會(huì)被配置,運(yùn)行時(shí)需要的信息在這個(gè)過程中被配置到Projec對(duì)象中狮含。最重要的是顽悼,在build script中定義的task將在這個(gè)過程創(chuàng)建曼振,并被初始化。需要注意的是蔚龙,在一般情況下冰评,只要在初始化階段創(chuàng)建的Project對(duì)象都會(huì)被配置,即使這個(gè)工程沒有參與本次構(gòu)建木羹。

執(zhí)行階段:

按照 2 中建立的有向無循環(huán)圖來執(zhí)行每一個(gè) task 甲雅,整個(gè)編譯過程中,這一步基本會(huì)占去 9 成以上的時(shí)間坑填,尤其是對(duì)于 Android 項(xiàng)目來講抛人,將 java 轉(zhuǎn)為 class.

8.項(xiàng)目應(yīng)用

a. Gradle生命周期回調(diào)

gradle提供了對(duì)project狀態(tài)配置監(jiān)聽的接口回調(diào),以方便我們來配置一些Project的配置屬性,監(jiān)聽主要分為兩大類,一種是通過project進(jìn)行回調(diào),一種是通過gradle進(jìn)行回調(diào)脐瑰。

作用域:

  • project是只針對(duì)當(dāng)前project實(shí)現(xiàn)進(jìn)行的監(jiān)聽
  • gradle監(jiān)聽是針對(duì)于所有的project而言的

下面我們通過一個(gè)例子來看看gradle的構(gòu)建生命周期究竟是怎么樣的妖枚。

項(xiàng)目結(jié)構(gòu)

root Project:GradleDeepTest
    -- app
        build.gradle
    -- testlibrary
        build.gradle
    build.gradle
    settings.gradle

root/settings.gradle

println "#### setting srcipt execute "
include ':app', ':testlibrary'

root/build.gradle

println "#### root build.gradle execute "

def buildVersion = '1.2.0'
def author = 'liuboyu'
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.0'
    }
}

allprojects {
    repositories {
        jcenter()
    }
//    it.setVersion(buildVersion)
//    println "this project_${delegate.name}_${delegate.getVersion()} is created by ${author}"
}


gradle.addProjectEvaluationListener(new ProjectEvaluationListener() {
    @Override
    void beforeEvaluate(Project project) {
        println "ROOT gradle_Project lifecycle : beforeEvaluate ${project.name} evaluate "
    }

    @Override
    void afterEvaluate(Project project, ProjectState state) {
        println "ROOT gradle_Project lifecycle : afterEvaluate ${project.name} evaluate "
    }
})

gradle.addBuildListener(new BuildListener() {
    @Override
    void buildStarted(Gradle gradle) {
        println "==ROOT== gradle Build lifecycle : buildStarted ${project.name} evaluate "
    }

    @Override
    void settingsEvaluated(Settings settings) {
        println "==ROOT== gradle Build lifecycle : settingsEvaluated ${project.name} evaluate "
    }

    @Override
    void projectsLoaded(Gradle gradle) {
        println "==ROOT== gradle Build lifecycle : projectsLoaded ${project.name} evaluate "
    }

    @Override
    void projectsEvaluated(Gradle gradle) {
        println "==ROOT== gradle Build lifecycle : projectsEvaluated ${project.name} evaluate "
    }

    @Override
    void buildFinished(BuildResult result) {
        println "==ROOT== gradle Build lifecycle : buildFinished ${project.name} evaluate "
    }
})

root/app/build.gradle

println "#### app build.gradle execute "
apply plugin: 'com.android.application'

android {
    compileSdkVersion 27
    buildToolsVersion "26.0.2"

    defaultConfig {
        applicationId "test.project.com.gradledeeptest"
        minSdkVersion 15
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

}

project.afterEvaluate {
    println "%%%% app lifecycle : afterEvaluate ${project.name} evaluate "
}

project.beforeEvaluate {
    println "%%%% app lifecycle : beforeEvaluate ${project.name} evaluate "
}


dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:22.2.0'
    compile 'com.android.support:design:22.2.0'
}

root/testlibrary/build.gradle

println "#### library build.gradle execute "
apply plugin: 'com.android.library'

android {
    compileSdkVersion 27
    buildToolsVersion "26.0.2"

    defaultConfig {
        minSdkVersion 15
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

project.afterEvaluate {
    println "@@@@ testlibrary lifecycle : afterEvaluate ${project.name} evaluate "
}

project.beforeEvaluate {
    println "@@@@ testlibrary lifecycle : beforeEvaluate ${project.name} evaluate "
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:22.2.0'
}

運(yùn)行結(jié)果

#### setting srcipt execute  // settings.gradle 加載
#### root build.gradle execute  // root build.gradle 加載
ROOT gradle_Project lifecycle : afterEvaluate GradleDeepTest evaluate  // root project 配置完成
ROOT gradle_Project lifecycle : beforeEvaluate testlibrary evaluate  // testlibrary project 開始配置
#### library build.gradle execute  // library build.gradle 加載
ROOT gradle_Project lifecycle : afterEvaluate testlibrary evaluate  // testlibrary project 配置完成
@@@@ testlibrary lifecycle : afterEvaluate testlibrary evaluate  // testlibrary project 配置完成
ROOT gradle_Project lifecycle : beforeEvaluate app evaluate  // app project 開始配置
#### app build.gradle execute // app build.gradle 加載
ROOT gradle_Project lifecycle : afterEvaluate app evaluate  // app project 配置完成
%%%% app lifecycle : afterEvaluate app evaluate  // app project 配置完成
==ROOT== gradle Build lifecycle : projectsEvaluated GradleDeepTest evaluate  // root project 配置完成
...
BUILD SUCCESSFUL in 2s
==ROOT== gradle Build lifecycle : buildFinished GradleDeepTest evaluate // root project build完成

ps:小弟不才,有哪里說的不對(duì)的苍在,歡迎指正~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末绝页,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子寂恬,更是在濱河造成了極大的恐慌续誉,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件初肉,死亡現(xiàn)場(chǎng)離奇詭異酷鸦,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)朴译,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門井佑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人眠寿,你說我怎么就攤上這事〗鼓ⅲ” “怎么了盯拱?”我有些...
    開封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長例嘱。 經(jīng)常有香客問我狡逢,道長,這世上最難降的妖魔是什么拼卵? 我笑而不...
    開封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任奢浑,我火速辦了婚禮,結(jié)果婚禮上腋腮,老公的妹妹穿的比我還像新娘雀彼。我一直安慰自己壤蚜,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開白布徊哑。 她就那樣靜靜地躺著袜刷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪莺丑。 梳的紋絲不亂的頭發(fā)上著蟹,一...
    開封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音梢莽,去河邊找鬼萧豆。 笑死,一個(gè)胖子當(dāng)著我的面吹牛昏名,可吹牛的內(nèi)容都是我干的涮雷。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼葡粒,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼份殿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起嗽交,我...
    開封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤卿嘲,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后夫壁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拾枣,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年盒让,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了梅肤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡邑茄,死狀恐怖姨蝴,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情肺缕,我是刑警寧澤左医,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站同木,受9級(jí)特大地震影響浮梢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜彤路,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一秕硝、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧洲尊,春花似錦远豺、人聲如沸奈偏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽霎苗。三九已至,卻和暖如春榛做,著一層夾襖步出監(jiān)牢的瞬間唁盏,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來泰國打工检眯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留厘擂,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓锰瘸,卻偏偏與公主長得像刽严,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子避凝,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容