用過android studio的對gradle應該都不陌生了,gradle文件的基本配置大同小異,略做了解使用應該是沒什么問題了。但是深入細致的了解一下對於理解項目還是很有幫助的,尤其是遇到一些配置復雜的github項目,不了解gradle可能會遇到跑不起來又束手無策的情形。下面對gradle相關知識、用法做一下總結。
DSL (domain specific language)
即所謂領域專用語言,其基本思想是“求專不求全”,不像通用目的語言那樣目標范圍涵蓋一切軟件問題,而是專門針對某一特定問題的計算機語言。
-DSL之於程序員正如伽南地之於以色列人,是最初也是最終的夢想。幾乎自計算機發明伊始,人們就開始談論DSL使用DSL了。
-前幾年迅速走紅的Ruby on Rails就被譽為“Web開發領域專用語言”
DSL 約等於 整潔的代碼
從概念上說,程序的編寫過程就是把業務領域中的問題通過代碼或者程序模型表達出來:
計算機的程序模型較為單一(歸根結底都是運算和存儲)
在面向對象技術成為主流的今天,通常情況下,計算機程序不太可能做到與業務領域中的概念一致,或者具有某些直覺的對應。因此,軟件的修改和可維護性並沒有想象中的容易。我們必須不斷地將業務領域中的概念轉換成相應的代碼模型,然后再進行修改。這種間接性直接造成了軟件的復雜度。
而DSL的主要目的就是要消除這樣的復雜度(或者說,以構造DSL的復雜度代替這種復雜度),DSL就要是要以貼近業務領域的方式來構造軟件。因此,DSL的簡潔性往往是一種思維上的簡潔性,使我們不用費太多的氣力就能看懂代碼所對應的業務含義。
DSL多以文本代碼的形式出現
多年來軟件工程實踐表明文本代碼是最有效率的編輯形式。但是一些特殊領域,文本代碼並不是最佳的表現形式,為了更好的貼近業務領域中的概念,我們可能會選擇使用一些圖形化的DSL。如:[DSM(Domain Specific Modeling)工具GEMS(Generic Eclipse Modeling System)中就大量地使用了不同的圖形化的DSL來表述系統的各個不同側面。
Gradle向我們提供了一整套DSL,所以在很多時候我們寫的代碼似乎已經脫離了groovy,但是在底層依然是執行的groovy
為了從命令行運行gradle測試樣例,首先
配置環境變量
- 創建變量名:GRADLE_HOME ,變量值:
C:\Users\jjx.gradle\wrapper\dists\gradle-2.5-all\d3xh0kipe7wr2bvnx5sk0hao8\gradle-2.5 - 加入path
;%GRADLE_HOME%\bin; - 檢查,如下就ok。
1
2
3
4
5
6
7
8
9
10
11
12
13
14C:\Users\jjx>gradle -v
------------------------------------------------------------
Gradle 2.5
------------------------------------------------------------
Build time: 2015-07-08 07:38:37 UTC
Build number: none
Revision: 093765bccd3ee722ed5310583e5ed140688a8c2b
Groovy: 2.3.10
Ant: Apache Ant(TM) version 1.9.3 compiled on December 23 2013
JVM: 1.7.0_75 (Oracle Corporation 24.75-b04)
OS: Windows 7 6.1 amd64
初嘗禁果
在d:盤建個文件夾Test,文件夾下建個文件build.gradle
打開文件,寫個簡單的代碼
1 |
task helloWorld << { |
打開cmd, d: 執行gradle helloWorld
1 |
C:\Users\jjx>d: |
同時,這時候發現已經自動在Test目錄下創建了.gradle文件。
上面helloWorld后的“<<”表示追加的意思,即向helloWorld中加入執行過程
使用doLast可以達到同樣效果
1 |
task helloWorldTwo { |
如果需要向Task的最前面加入執行過程,我們可以使用doFirst:
1 |
task helloWorldThree { |
耶,懂了!
關於 task
Gradle將當前目錄下的build.gradle文件作為項目的構建文件。在上面的例子中,我們創建了一個名為helloWorld的Task,在執行gradle命令時,我們指定執行這個helloWorld Task。這里的helloWorld是一個DefaultTask類型的對象,這也是定義一個Task時的默認類型,當然我們也可以顯式地聲明Task的類型,甚至可以自定義一個Task類型
下面再看一個小case:
我們在Test文件夾下建一個src目錄,建一個dst目錄,src目錄下建立一個文件,命名為here.txt
然后在build.gradle中append一個task:
1 |
task helloWorld << { |
代碼中(type:Copy)就是“顯式地聲明Task的類型”,helloworld沒有就是默認得DefaultTask類型咯。
然后cmd中執行命令
1 |
D:\Test>gradle copyFile |
好了! here.txt也跑到dst中去啦!簡單吧!
加一把火,我們來看一下當前目錄下(即Test目錄文件,這里也可以將這個目錄理解為一個project,不過還沒寫有生產力的代碼,哈哈哈)定義的task
1 |
D:\Test>gradle tasks |
是不是很清晰呢,展示了各種task類型,task的作用,以及另外兩個task相關的命令,相信聰明的你也一看就懂。
Gradle本身的領域對象主要有Project和Task。
Project為Task提供了執行上下文,所有的Plugin要么向Project中添加用於配置的Property,要么向Project中添加不同的Task。
一個Task表示一個邏輯上較為獨立的執行過程,比如編譯Java源代碼,拷貝文件,打包Jar文件,甚至可以是執行一個系統命令或者調用Ant。另外,一個Task可以讀取和設置Project的Property以完成特定的操作。是不是很屌的樣子。
task關鍵字其實是一個groovy中方法的調用,該方法屬於Project,而大括號之間的內容則表示傳遞給task()方法的一個閉包。
task間的依賴
task之間是可以存在依賴關系,比如TaskA依賴TaskB,那么在執行TaskA時,Gradle會先執行TaskB,再執行TaskA。我們可以在定義一個Task的同時聲明它的依賴關系:
1 |
task helloWorldFour(dependsOn:helloWorldThree) << { |
或者
1 |
task helloWorldFour << { |
可配置的task
一個Task除了執行操作之外,還可以包含多個Property,其中有Gradle為每個Task默認定義了一些Property,比如description,logger等。
另外,每一個特定的Task類型還可以含有特定的Property,比如Copy的from和to等。
當然,我們還可以動態地向Task中加入額外的Property。在執行一個Task之前,我們通常都需要先設定Property的值。
1 |
task helloWorld << { |
或者通過調用Task的configure()方法完成Property的設置:
1 |
task helloWorld << { |
花式task
1 |
task showDescription1 << { |
對於每一個Task,Gradle都會在Project中創建一個同名的Property,所以我們可以將該Task當作Property來訪問,showDescription2便是這種情況。另外,Gradle還會創建一個同名的方法,該方法接受一個閉包,我們可以使用該方法來配置Task,showDescription3便是這種情況。
耶!簡單吧
關於 Groovy
Gradle是一種聲明式的構建工具。
在執行時,Gradle並不會一開始便順序執行build.gradle文件中的內容,而是分為兩個階段,第一個階段是配置階段,然后才是實際的執行階段。
配置階段,Gradle將讀取所有build.gradle文件的所有內容來配置Project和Task等,比如設置Project和Task的Property,處理Task之間的依賴關系等。
Gradle的DSL只是Groovy語言的內部DSL,也必須遵循Groovy的語法規則。
Groovy語言中的兩個概念,一個是Groovy中的Bean概念,一個是Groovy閉包的delegate機制。
bean
Groovy中的Bean和Java中的Bean有一個很大的不同,即Groovy動態的為每一個字段都會自動生成getter和setter,並且我們可以通過像訪問字段本身一樣調用getter和setter
1 |
class GroovyBeanExample { |
采用像直接訪問的方式的目的是為了增加代碼的可讀性,使它更加自然,而在內部,Groovy依然
是在調用setter和getter方法。
閉包的delegate機制
簡單來說,delegate機制可以使我們將一個閉包中的執行代碼的作用對象設置成任意其他對象。
1 |
class Child { |
在上面的例子中,當調用configChild()方法時,並沒有指出name屬性是屬於Child的,但是它的確是在設置Child的name屬性。
事實上光從該方法的調用中,我們根本不知道name是屬於哪個對象的,你可能會認為它是屬於Parent的。
真實情況是,在默認情況下,name的確被認為是屬於Parent的,但是我們在configChild()方法的定義中做了手腳,使其不再訪問Parent中的name(Parent也沒有name屬性),而是Child的name。
在configChild()方法中,我們將該方法接受的閉包的delegate設置成了child,然后將該閉包的ResolveStrategy設置成了DELEGATE_FIRST。這樣,在調用configChild()時,所跟閉包中代碼被代理到了child上,即這些代碼實際上是在child上執行的。
此外,閉包的ResolveStrategy在默認情況下是OWNER_FIRST,即它會先查找閉包的owner(這里即parent),如果owner存在,則在owner上執行閉包中的代碼。這里我們將其設置成了DELEGATE_FIRST,即該閉包會首先查找delegate(本例中即child),如果找到,該閉包便會在delegate上執行。
聯想gradle中聲明的方法
在使用Gradle時,我們並沒有像上面的parent.configChild()一樣指明方法調用的對象,而是在build.gradle文件中直接調用task(),apply()和configuration()等方法。這是
因為在沒有說明調用對象的情況下,Gradle會自動將調用對象設置成當前Project。
比如調用apply()方法和調用project.apply()方法的效果是一樣的。查查Gradle的Project文檔,你會發現這些方法都是Project類的方法。
對於configurations()方法,該方法實際上會將所跟閉包的delegate設置成ConfigurationContainer,然后在該ConfigurationContainer上執行閉包中的代碼。再比如,dependencies()方法,該方法會將所跟閉包的delegate設置成DependencyHandler。
終於到了gradle
自定義Property
Gradle還為我們提供了多種方法來自定義Project的Property。
在build.gradle文件中定義Property
添加一個名為property1的Property:
1 |
ext.property1 = "this is property1" |
或者采用閉包的形式
1 |
ext { |
定義了Property后,使用這些Property時我們則不需要ext,而是可以直接訪問:
1 |
task showProperties << { |
還可以在執行命令行的時候加屬性
1 |
task showCommandLieProperties << { |
此外還可以通過環境變量來為Gradle設置Property,但是每一個Property都需要以“ORG_GRADLEPROJECT”為前綴:
1 |
ORG_GRADLE_PROJECT_property3="this is yet another property3" |
Gradle 的 Plugin
Gradle最常用的Plugin便是java Plugin了。和其他Plugin一樣,java Plugin並沒有什么特別的地方,只是向Project中引入了多個Task和Property。當然,java Plugin也有比較與眾不同的地方,其中之一便是它在項目中引入了構建生命周期的概念,就像Maven一樣。但是,和Maven不同的是,Gradle的項目構建生命周期並不是Gradle的內建機制,而是由Plugin自己引入的。
依賴管理
一個項目總會依賴於第三方,要么是一個第三方類庫,要么是自己開發的另一個module
配置Gradle的Repository,就是告訴Gradle在什么地方去獲取這些依賴
1 |
repositories { |
jCentral()是大於mavenCentral()的一個倉庫,現在是studio默認的倉庫
Gradle對依賴進行分組,允許編譯時使用一組依賴,運行時使用另一組依賴。每一組依賴稱為一個Configuration,在聲明依賴時,我們實際上是在設置不同的Configuration。
要定義一個Configuration,我們可以通過以下方式完成:studio一般不需要設置,應該是有默認的,即為classpath
1 |
configurations { |
通過dependencies()方法向myDependency中加入實際的依賴項:
1 |
dependencies { |
myDependency,classpath,compile,testCompile都是Configuration(一組依賴)。
除了myDependency都不使我們定義的,為啥呢,android Plugin會自動定義compile和testCompile分別用於編譯Java源文件和編譯Java測試源文件。classpath應該是用於所有,我類推的。
Gradle還允許我們聲明對其他Project或者文件系統的依賴。
1 |
dependencies { |
對於本地文件系統中的Jar文件,我們可以通過以下方式聲明對其的依賴:
1 |
dependencies { |
構建多module的project
Gradle為每個build.gradle都會創建一個相應的module領域對象,在編寫Gradle腳本時,我們實際上是在操作諸如module這樣的Gradle領域對象。在多module的項目中,我們會操作多個module領域對象。Gradle提供了強大的多module構建支持
要創建多module的Gradle項目,我們首先需要在根(Root)Project中加入名為settings.gradle的配置文件,該文件應該包含各個子module(其實就是一個子project)的名稱。如setting.gradle中:
1 |
include 'library', 'demo' |
類似module(子project)的build.gradle,(Root)Project也有自己的build.gradle,在里面通常設置:
1 |
allprojects { |
allprojects()方法將repositories配置一次性地應用於所有的module(子Project)和root-project本身,當然也包括定義的Task,這個task配置到所有module里面了和root-project。
subprojects()方法用於配置所有的子Project(不包含根Project)
步入巔峰
Gradle本身只是一個架子,真正起作用的是Task和Plugin。
自定義Task
Gradle中的Task要么是由不同的Plugin引入的,要么是我們自己在build.gradle文件中直接創建的。
-
在build.gradle文件中直接定義
需要定義的Task類型不多時
Gradle其實就是groovy代碼,所以在build.gradle文件中,我們便可以定義Task類。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class HelloWorldTask extends DefaultTask {
//@Optional,表示在配置該Task時,message是可選的。
@Optional
String message = 'I am jjx'
//@TaskAction表示該Task要執行的動作,即在調用該Task時,hello()方法將被執行
@TaskAction
def hello(){
println "hello world $message"
}
}
//hello使用了默認的message值
task hello(type:HelloWorldTask)
//重新設置了message的值
task helloOne(type:HelloWorldTask){
message ="I am a android developer"
} -
在當前工程中定義Task類型
只能應用在當前module中,沒什么卵用,下面是全局可用的 - 在單獨的項目中定義Task類型
項目中存在大量的自定義Task類型時,在另外的一個gradle文件中定義這些Task,然后再apply到build.gradle文件中。
可以參考印象筆記的demo:https://github.com/evernote/evernote-sdk-android
中的:1
2
3
4//這是插件
apply plugin: 'com.android.application'
//這里gradle-quality.gradle就是另外單獨定義了task的gradle
apply from: '../build-config/gradle-quality.gradle'
自定義Plugin
與自定義task極其類似,可以類推理解,也是有3中方式定義,只是代碼不一樣:
1 |
apply plugin: DateAndTimePlugin |
每一個自定義的Plugin都需要實現Plugin接口,除了給Project編寫Plugin之外,我們還可以為其他Gradle類編寫Plugin。
以上是在build.gradle文件中直接定義Plugin,還可以在當前工程中、單獨的項目中創建Plugin,一般情況不需要了解。
怎么樣,通過以上學習,再看android studio的gradle代碼是不是小case了呢!