Gradle的學習總結

chg668 8年前發布 | 32K 次閱讀 Gradle 安卓開發 項目構建

前言:

之前寫了一篇關于Gradle渠道定制,雖然說按照官方API可以實現我們的相關的定制化需求,但是對于里面的一些Gradle一些知識并沒有特別了解,比如為什么Gradle可以幫我們完成我們定制化的需求,我們在Gradle中Android{ }括號內為什么可以填寫buildTypes、productFlavor等等,這些都是讓我感到很困惑的地方,經過前一段時間對Gradle的學習,在此做個學習總結,同時也希望能幫助到大家。

目錄

1.什么是Gradle

2.Grovvy初探

(1)基本數據類型和容器

(2)基本語法

(3)閉包

(4)Grovvy的建構者模式(Grovvy實現建構的方式)

3.Gradle建構

(1)Gradle工作流程

(2)Gradle三種重要的對象

(3)Gradle Task

(4)三個例子

什么是Gradle?

一個建構工具,同時它也是一個編程框架。

什么是建構?

簡單來說就是輸入信息,執行命令,得到產物。在項目中建構就是告訴我們的項目,哪些是源碼,哪些是資源,編譯文件,如何打包......完成這一系列工作,得到最后的產物。Gradle是一個新型的建構的工具,它所使用Grovvy比傳統的XML更具優勢,可以實現if{某條件成立,編譯某文件}/else{編譯其他文件}這樣有不同條件的任務。Gradle另外一個特點就是它是一種DSL,即Domain Specific Language,領域相關語言。什么是DSL,說白了它是某個行業中的行話。所以我們需要明白的一點是,我們編寫腳本是離不開API文檔的。

構建工具和編程框架

對我們是用來說,熟悉API,通過API寫建構腳本,那么就是一個工具,比如說我們的使用Android gradle plugin對我們來說就是工具。

我們項目中比如使用了微信的AndResGuard(資源混淆壓縮),是使用gradle編寫的插件,對于他們來說Gradle就是編程框架。

在這里,對我們項目需求來看,我們只需將其作為工具看待,了解API,完成我們的項目構建即可。但是只知其燃不知其所以燃顯然不是我們程序員的風格,了解一些必要的原理,可以幫助我們更好地理解Gradle。

Gradle 是使用Grovvy語言來進行構建的,理解gradle就有必要了解Grovvy語言。

Grovvy初探

API文檔: http://docs.groovy-lang.org/latest/html/groovy-jdk/

Grovvy語言是一種JVM的動態語言。Groovy語言內部會將其編譯成Java class然后啟動虛擬機來執行。作為動態語言,Grovvy世界中的所有事物都是對象。

1.基本數據類型和容器

跟java基本數據類型相同

Grovvy中容器(三種)

List:鏈表,其底層對應Java中的List接口,一般用ArrayList作為真正的實現類。

Map:鍵-值表,其底層對應Java中的LinkedHashMap。

Range:范圍,它其實是List的一種拓展

2.基本語法

定義變量

def  variable1 = 1   // 定義一個變量,可不用指定類型,結尾換行不用分號
def  int x = 1   //變量定義時,也可以直接指定類型
def  aList = [5,'string',true]  // 定義數組
def  aMap = ['key1':'value1','key2':true] // 定義Map
def  aRange = 1..5  //Range類型的變量 由begin值+兩個點+end值表示
                      左邊這個aRange包含1,2,3,4,5這5個值
println aRange.from  //1
println aRange.to  //5

方法定義

def  nonReturnTypeFunc(){
     last_line   //最后一行代碼的執行結果就是本函數的返回值
}
String testFunction(arg1,arg2){ //無需指定參數類型
  ...
}

注意:根據Groovy的原則,如果一個類中有名為xxyyzz這樣的屬性(其實就是成員變量),Groovy會自動為它添加getXxyyzz和setXxyyzz兩個函數,用于獲取和設置xxyyzz屬性值。

例如:上面例子aRange.from 和aRange.to

其實Range類中存在這個兩個參數

但是有沒有發現,這兩個變量是private的,而aRang.to和aRange.from其實是執行了getTo和getFrom的方法!

3.閉包

閉包,是一種 數據類型 ,它代表了一段可執行的代碼。從C/C++語言的角度看,閉包和函數指針很像

閉包的定義:

def aClosure = { //閉包是一段代碼,所以需要用花括號括起來..  
    String param1, int param2 ->  //箭頭很關鍵。箭頭前面是參數定義,箭頭后面是代碼  
    println "this is code" //這是代碼,最后一句是返回值,  
   //也可以使用return,和Groovy中普通函數一樣  
}

使用:

aClosure.call("this is string",100) 或者

aClosure("this is string", 100)

更多時候,閉包將以一個方法的參數使用。

類似我們的回調。

回調回調!!

閉包作為參數

當這個方法有個閉包的參數作為最后一個參數時有個特點: 省略圓括號

比如定義一個數組:

def aList = [1,2,3,4,5]

一般我們可能這樣去執行each方法:

aList.each({
    item->println(item)
})

更多的是使用省略圓括號的方式:

aList.each{ //這行這個數組的each函數,這個each函數傳入一個閉包
    item->println item //item是閉包的參數
}

關于上面的這個例子,這里需要理解兩個點:

1.each(Closure closure)這個方法需要傳入一個為閉包數據類型的參數

2.這個closure參數它也會接受一個參數,這個參數是調用者調用這個each方法的時候傳入的,上面的item就是這個closure接收的參數,然后把item打印出來

問題:這個傳入閉包的item參數是哪里來的呢?

我們先查看API文檔,查看方法名

each方法

方法解釋,遍歷這個List,把 每一個元素 傳遞給這個閉包!

所以上面的方法是遍歷的了這個aList,打印了每一個元素。

所以Closure的使用依賴于你對API的熟悉程度,但是還有一個約定俗成方式去判斷傳入閉包的參數,就是看方法名,一般我們可以通過方法名去判斷傳入閉包的參數,例如上面的each就是把每一個元素傳遞閉包。

4.Grovvy的建構者模式(Grovvy實現建構的方式)

這里主要介紹為什么在閉包里面的填寫的參數可以設置成自己定義的參數,有興趣可以去查看一下上面的鏈接,這里節選了建構者這一章節。

Groovy 是構建 DSL 的一種選擇平臺。使用閉包可以非常輕松地創建自定義控制結構,創建構建者也非常方便,我們很常可以看到這樣的定義:

email {
    from 'dsl-guru@mycompany.com'
    to 'john.doe@waitaminute.com'
    subject 'The pope has resigned!'
    body {
        p 'Really, the pope has resigned!'
    }
}

使用構建者策略可實現,利用一個參數為閉包的名為 email 的方法,它會將隨后的調用委托給一個對象,該對象實現了 from、to、subject 及 body 各方法。body 方法使用閉包做參數,使用的是構建者策略。

實現這樣的構建者往往要通過下面的方式:

def email(Closure cl) {
    def email = new EmailSpec()
    def code = cl.rehydrate(email, this, this)
    code.resolveStrategy = Closure.DELEGATE_ONLY
    code()
}

EmailSpec 類實現了 from、to 等方法,通過調用 rehydrate,創建了一個閉包副本,用于為該副本設置 delegate、owner 及 thisObject 等值。設置 owner 和 thisObject 并不十分重要,因為將使用 DELEGATE_ONLY 策略,解決方法調用只針對的是閉包委托。

class EmailSpec {
    void from(String from) { println "From: $from"}
    void to(String... to) { println "To: $to"}
    void subject(String subject) { println "Subject: $subject"}
    void body(Closure body) {
        def bodySpec = new BodySpec()
        def code = body.rehydrate(bodySpec, this, this)
        code.resolveStrategy = Closure.DELEGATE_ONLY
        code()
    }
}

The EmailSpec 類自身的 body 方法將接受一個復制并執行的閉包,這就是 Groovy 構建者模式的原理。

代碼中的一個問題在于,email 方法的用戶并不知道他能在閉包內調用的方法。唯一的了解途徑大概就是方法文檔。

還有一個方式是,編譯時解釋委托策略@DelegatesTo。

def email(@DelegatesTo(EmailSpec) Closure cl) {
    def email = new EmailSpec()
    def code = cl.rehydrate(email, this, this)
    code.resolveStrategy = Closure.DELEGATE_ONLY
    code()
}

大致的作用就是通過這個@DelegatesTo 讓編譯器知道這和Closure代理給誰,可以更智能顯示填寫哪些參數,方便編程,在這里就不擴展了。

通過上面我們了解了,Grovvy的基本語法和閉包的基本使用,了解Grovvy建構的基本概念是通過代理來完成,那么在Gradle是怎么使用Grovvy來完成我們的構建呢?

Gradle建構

Gradle是一個框架,它定義一套自己的游戲規則,必須要遵守它設計的規則。

Gradle中,每一個待編譯的工程都叫一個Project。每一個Project在構建的時候都包含一系列的Task。比如一個Android APK的編譯可能包含:Java源碼編譯Task、資源編譯Task、JNI編譯Task、lint檢查Task、打包生成APK的Task、簽名Task等。一個具體的編譯過程是由一個一個的Task來定義和執行的。

我們首先通過項目工程目錄,來理解Gradle設計的規則

settings.gradle表示導入的哪些項目,具體來說就是gradle需要編譯哪些項目。

如圖,導入了app這個工程。

在主目錄MyApplication工程目錄下有個build.gradle

我們看到最上面的注釋,這個文件是level最高級的配置文件,這里配置Android gradle plugin版本,這里我的是2.2.0的版本,allprojects配置所有Project的需要的配置。

為了解決Gradle版本不斷升級,不同時期的項目編譯不同版本的Gradle,Google推出了gradle wrapper,這個就放置在gradle文件夾中。

注意:這里dependencies配置的是Android-gradle-plugin版本而不是Gradle的版本。

擴展: 導入開源項目的正確姿勢

在我們導入GitHub項目的時候經常會遇到一直在下載Gradle,打不開項目的問題。原因就是要導入的項目Android-gradle-plugin和Gradle版本本地不存在,所以導入項目是會先去下載,但是下載又因為墻的原因就會導致在載入界面卡主。導入項目正確的姿勢是:

1.打開本地可以編譯的項目,

2.將這個項目中的依賴的Android-gradle-plugin版本和gradle目錄拷貝替換到需要導入的項目中就可以打開這個項目了。

繼續查看項目目錄

在app目錄中也存在一個build.gradle文件,內容后面再講解。其實每一個setting.gradle 中include進來的Project都需要配置build.gradle文件。

之前我們提到導入的每一個項目都是一個Project,每個Project又包含了許多Task。查看Task,可以在終端執行gradlew tasks,或者通過studio提供可視化工具操作。

之前說到的這些Task包含Java源碼編譯Task、資源編譯Task、JNI編譯Task、lint檢查Task、打包生成APK的Task、簽名Task等等。

了解了Gradle的每個Project包含了Task,那么編譯的過程是怎么把Task聯系起來呢?

1.Gradle工作流程

1.(Initiliazation phase)首先是初始化階段。就是執行settings.gradle Initiliazation phase的下一個階段是Configration階段。

2.(Configration phase)Configration階段的目標是解析每個project中的build.gradle。比如上面例子中,解析每個子目錄中的build.gradle。在這兩個階段之間,我們可以加一些定制化的Hook(鉤子)。這當然是通過API來添加的。

3.(Execution phase)Configuration階段完了后,整個build的project以及內部的Task關系就確定了。前面說過,一個Project包含很多Task,每個Task之間有依賴關系。Configuration會建立一個 有向圖來描述Task之間的依賴關系 。所以,我們可以添加一個HOOK,即當Task關系圖建立好后,執行一些操作。

關于Gradle的工作流程,我們需要記住:

Gradle有一個初始化流程,這個時候settings.gradle會執行。

在配置階段,每個Project都會被解析,其內部的任務也會被添加到一個有向圖里,用于解決執行過程中的依賴關系。

然后才是執行階段。你在gradlew xxx中指定什么任務,gradle就會將這個xxx任務鏈上的所有任務全部按依賴順序執行一遍!

2.Gradle三種重要的對象

Gradle對象:當我們執行gradlew xxx或者什么的時候,gradle會從默認的配置腳本中構造出一個Gradle對象。在整個執行過程中,只有這么一個對象。Gradle對象的數據類型就是Gradle。我們一般很少去定制這個默認的配置腳本。

Project對象:每一個build.gradle會轉換成一個Project對象。加載插件,設置不同的配置,比如Application plugin插件和Library plugin插件gradle配置是不太一樣的。

Settings對象:顯然,每一個settings.gradle都會轉換成一個Settings對象。

Project對象:

文檔: https://docs.gradle.org/current/dsl/org.gradle.api.Project.html

我們知道每一個項目在build.gradle腳本解析成一個Project對象,在build.gradle我們可以查看這個Project的變量,Project包含了許多Task,tasks就是任務的集合

3.Gradle Task

Gradle Task是Gradle非常重要的概念,Task是Gradle中的一種數據類型。它代表了一些要執行或者要干的工作。不同的插件可以添加不同的Task。每一個Task都需要和一個Project關聯。

(Task的API文檔位于 https://docs.gradle.org/current/dsl/org.gradle.api.Task.html)

Task 定義(這個是在Project中定義的,Project文檔!)

task helloworld {  //調用的是Project中的方法task(name, configureClosure)
    println('hello world!')
    doFirst{
        println('do first')
    }
    do Last{
        println('do last')
    }
}

上面代碼的意思就是定義了一個Task,Task的名字為helloworld

doFirst和doLast都給這個Task添加了Action。

注意:上面println('hello world')會在配置階段就會執行,doFirst和doLast不會執行,通過執行gradlew helloworld才會順序執行doFirst和doLast。

<<符號代表 do last

task helloworld << {
    println('do last')
}

Task 依賴

testTask0.dependsOn testTask1  //執行testTask0將會先執行testTask1

4.三個例子:

這三個都是直接是寫在app工程下gradle中,project.afterEvalute意思是創建了任務有向圖之后。寫一下上面的代碼,編譯并運行gradlew assmbleDebug,查看輸出。深入理解上面例子的執行過程。

解析

第一個,編譯報錯。

上面說這個任務在app這個project中找不到,我們之前說過gradle是有生命周期的,我們在配置階段完成之后才會添加project中形成有向圖,這段代碼是在配置之前就執行了,有向圖沒有形成,自然找不到這個任務,從而報錯。

第二個,編譯時輸出。

我們看到在編譯過程就輸出了這個代碼,而且確實是在配置(Configuration)生命周期之后,因為我們并沒有給這個任務添加Action,所以println會在getByName方法中一并輸出。

第三個,編譯不輸出,執行gradlew assmbleDebug輸出

這是因為我們在這個Task添加doLast的Action,說明println的動作會在這個Task的最后執行,也就是我們執行這個assembleDebug任務的時候最后執行。

 

 

 

來自:http://www.jianshu.com/p/5671ecfdf115

 

 本文由用戶 chg668 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!