是什么?
在語法上是基於Groovy語言的(Groovy 是一種基於JVM的敏捷開發語言,可以簡單的理解為強類型語言java的弱類型版本),在項目管理上是基於Ant和Maven概念的項目自動化建構工具。
基礎知識准備
Java基礎,命令行使用基礎
官方文檔:https://docs.gradle.org/current/dsl/
Gradle使用指南: https://gradle.org/docs/current/userguide/userguide
Android插件文檔:https://github.com/google/android-gradle...
AndroidGradle使用文檔:http://tools.android.com/tech-docs/new-build-system/user-guide
Groovy基礎: http://attis-wong-163-com.iteye.com/blog/1239819
Groovy閉包的Delegate機制:https://www.cnblogs.com/zqlxtt/p/5741297.html
搭建Gradle運行環境
- Gradle 運行依賴JVM,也就是java運行的環境。所以要安裝jdk和jre,好像目前的Gradle的運行環境要求jdk的版本在1.6以上,應該的,現在jdk都到1.8了。
- 然后到Gradle官網現在Gradle的壓縮包。地址,這個頁面里面又兩種方式,一種手動安裝,一種通過腳本安裝。我一般喜歡自己動手,這樣將來清理起來比較方便。
- 下載壓縮包后,解壓,然后配置環境變量,手動安裝過jdk的人應該都配置環境變量很熟了吧。每個平台下配置環境變量的方式不一樣
MacOS 下配置。在~/.bash_profile
中添加如下代碼
#gradle 注意gradle-2.14.1是自己解壓的路徑
export GRADLE_HOME=${HOME}/gradle-2.14.1
PATH=${PATH}:${GRADLE_HOME}/bin
export PATH
保存后在終端輸入source ~/.bash_profile
回車執行讓剛剛的配置生效。然后命令行輸入gradle -v
查看是否安裝成功。
$ gradle -v
------------------------------------------------------------
Gradle 2.14.1
------------------------------------------------------------Build time: 2016-07-18 06:38:37 UTC
Revision: d9e2113d9fb05a5caabba61798bdb8dfdca83719
Groovy: 2.4.4
Ant: Apache Ant(TM) version 1.9.6 compiled on June 29 2015
JVM: 1.8.0_111 (Oracle Corporation 25.111-b14)
OS: Mac OS X 10.12.2 x86_64
弄一個HelloWorld看看
創建一個test_gralde
文件夾。然后在文件夾里面創建一個build.gradle
文件。注意文件名不要亂起。在build.gradle
中添加如下代碼:
task helloworld{
doLast{
println'Hello World!'
}
}
#后者等同於下面的代碼,
task helloworld2 <<{
println "Hello World!"
}
然后來運行一下:
liuqiangs-MacBook-Pro:test_gralde liuqiang$ gradle helloworld :helloworld Hello World!
BUILD SUCCESSFUL
Total time: 1.52 secs
This build could be faster, please consider using the Gradle Daemon: https://docs.gradle.org/2.14.1/userguide/gradle_daemon.html
我們分析一下執行步驟。build.gradle是Gradle默認的構建腳本文件,執行Gradle命令的時候,會默認加載當前目錄下的build.gradle腳本文件,當然你也可以通過 -b 參數指定想要加載執行的文件。這只是個最簡單的task例子,后面詳細介紹task的常見定義。
這個構建腳本定義一個任務(Task),任務名字叫helloworld,並且給任務helloworld添加了一個動作,官方名字是Action,閱讀Gradle源代碼你會到處見到它,其實他就是一段Groovy語言實現的閉包,doLast就意味着在Task執行完畢之后要回調doLast的這部分閉包的代碼實現。第二個方法中的“<<”表示向helloworld中加入執行代碼。至於語法部分,基本是Groovy語法(包括一些語法糖,也就是寫簡寫方式,如果寫個JavaScript或者Python會好理解一些,但是還是建議去讀一下groovy的基本語法),加上一些DSL(domain specific language)的約定。
執行流程和基本術語
和Maven一樣,Gradle只是提供了構建項目的一個框架,真正起作用的是Plugin。Gradle在默認情況下為我們提供了許多常用的Plugin,其中包括有構建Java項目的Plugin,還有Android等。與Maven不同的是,Gradle不提供內建的項目生命周期管理,只是java Plugin向Project中添加了許多Task,這些Task依次執行,為我們營造了一種如同Maven般項目構建周期。
Gradle是一種聲明式的構建工具。在執行時,Gradle並不會一開始便順序執行build.gradle文件中的內容,而是分為兩個階段,第一個階段是配置階段,然后才是實際的執行階段。
配置階段,Gradle將讀取所有build.gradle文件的所有內容來配置Project和Task等,比如設置Project和Task的Property,處理Task之間的依賴關系等。
看一個基本結構的Android多Moudule(也就是gradle中的多Project Multi-Projects Build)的基本項目結構。
├── app #Android App目錄
│ ├── app.iml
│ ├── build #構建輸出目錄
│ ├── build.gradle #構建腳本
│ ├── libs #so相關庫
│ ├── proguard-rules.pro #proguard混淆配置
│ └── src #源代碼,資源等
├── module #Android 另外一個module目錄
│ ├── module.iml
│ ├── build #構建輸出目錄
│ ├── build.gradle #構建腳本
│ ├── libs #so相關庫
│ ├── proguard-rules.pro #proguard混淆配置
│ └── src #源代碼,資源等
├── build
│ └── intermediates
├── build.gradle #工程構建文件
├── gradle
│ └── wrapper
├── gradle.properties #gradle的配置
├── gradlew #gradle wrapper linux shell腳本
├── gradlew.bat
├── LibSqlite.iml
├── local.properties #配置Androod SDK位置文件
└── settings.gradle #工程配置
上面的是完整的AndroidStudio中的項目結構,我們抽象成Gradle多個Project的樣子
├── app
│ ├── build.gradle #構建腳本
├── module
│ ├── build.gradle #構建腳本
├── build.gradle #工程構建文件
├── gradle
│ └── wrapper #先不去管它
├── gradle.properties #gradle的配置
├── gradlew #gradle wrapper linux shell腳本
├── gradlew.bat
└── settings.gradle #工程配置
- Gradle為每個build.gradle都會創建一個相應的Project領域對象,在編寫Gradle腳本時,我們實際上是在操作諸如Project這樣的Gradle領域對象。在多Project的項目中,我們會操作多個Project領域對象。Gradle提供了強大的多Project構建支持。
- 要創建多Project的Gradle項目,我們首先需要在根(Root)Project中加入名為settings.gradle的配置文件,該文件應該包含各個子Project的名稱。Gradle中的Project可以簡單的映射為AndroidStudio中的Module。
- 在最外層的build.gradle。一般干得活是:配置其他子Project的。比如為子Project添加一些屬性。
- 在項目根目錄下有個一個名為settings.gradle。這個文件很重要,名字必須是settings.gradle。它里邊用來告訴Gradle,這個multiprojects包含多少個子Project(可以理解為AndroidStudio中Module)。
讀懂Gradle配置語法
Gradle向我們提供了一整套DSL,所以在很多時候我們寫的代碼似乎已經脫離了groovy,但是在底層依然是執行的groovy所以很多語法還是Groovy的語法規則。
看一個AndroidStudio中app下的build.gradle的配置
apply plugin: 'com.android.application'
android {
compileSdkVersion 25
buildToolsVersion "25.0.0"
defaultConfig {
applicationId "me.febsky.demo"
minSdkVersion 15
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:25.1.0'
}
分析第一行apply plugin: 'com.android.application'
這句其實是Groovy語法糖,像Ruby和Js都有這種語法糖,apply實際上是個方法,補上括號后的腳本:apply (plugin: 'com.android.application')
,看起來還是有點別扭是不?還有個語法糖,如果方法參數是個map類型,那么方括號可以省略,進一步還原apply([ plugin: 'com.android.application'])
,不理解的可以去看下Groovy的map的寫法,和js一樣。所以這行的意思是:apply其實是個方法,接收一個Map類型的參數。
總結兩點:1. 方法調用,圓括號可以省略 2. 如果方法參數是個Map,方括號可以省略。
Groovy語言的閉包語法
看上面的dependencies
這其實是個方法調用。調用了Project的dependencies方法。只不過參數是個閉包,閉包的用法在文章開始給出了鏈接。我們對其進行還原一下:
#方法調用省略了()我們加上
dependencies ({
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:25.1.0'
})
提示一點:如果閉包是方法的最后一個參數,那么閉包可以放在圓括號外面
#所以代碼還能寫成這樣
dependencies (){
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:25.1.0'
}
Getter和Setter
Groovy語言中的兩個概念,一個是Groovy中的Bean概念,一個是Groovy閉包的Delegate機制。
Java程序員對JavaBeans和Getter/Setter方法肯定不陌生,被設計用來獲取/設置類的屬性。但在Groovy中就不用那些沒用的方法了。即Groovy動態的為每一個字段都會自動生成getter和setter,並且我們可以通過像訪問字段本身一樣調用getter和setter。比如Gradle的Project對象有個version屬性(Property)下面這兩行代碼執行結果是一樣的:
println project.version // Groovy
println(project.getVersion()) // Java
Project,Task ,Action
Gradle的Project之間的依賴關系是基於Task的,而不是整個Project的。
Project:是Gradle最重要的一個領域對象,我們寫的build.gradle腳本的全部作用,其實就是配置一個Project實例。在build.gradle腳本里,我們可以隱式的操縱Project實例,比如,apply插件、聲明依賴、定義Task等,如上面build.gradle
所示。apply、dependencies、task等實際上是Project的方法,參數是一個代碼塊。如果需要,也可以顯示的操縱Project實例,比如:project.ext.myProp = 'myValue'
Task:被組織成了一個有向無環圖(DAG)。Gradle中的Task要么是由不同的Plugin引入的,要么是我們自己在build.gradle文件中直接創建的。Gradle保證Task按照依賴順序執行,並且每個Task最多只被執行一次。
Gradle在默認情況下為我們提供了幾個常用的Task,比如查看Project的Properties、顯示當前Project中定義的所有Task等。可以通過一下命令行查看Project中所有的Task:$ gradle tasks
(具體log不再貼出來)。可以看到,Gradle默認為我們提供了dependencies、projects和properties
等Task。dependencies用於顯示Project的依賴信息,projects用於顯示所有Project,包括根Project和子Project,而properties則用於顯示一個Project所包含的所有Property。
**Tips: **查看Project中所有的Task:
$ gradle tasks
查看Project中所有的properties:$ gradle properties
在上面的build.gradle
中加入如下代碼:
task myTask {
doFirst {
println 'hello'
}
doLast {
println 'world'
}
}
這段代碼的含義:給Project添加一個名為“myTask”的任務
用一個閉包來配置這個任務,Task提供了doFirst和doLast方法來給自己添加Action。
其實build.gradle腳本的真正作用,就是配置一個Project實例。在執行build腳本之前,Gradle會為我們准備好一個Project實例,執行完腳本之后,Gradle會按照DAG依次執行任務。
自定義Task的寫法
看下面代碼文件路徑~/Test/build.gradle
:
#1
task helloWorld << {
println "Hello World"
}
#2 Test文件夾下建一個src目錄,建一個dst目錄,src目錄下建立一個文件,命名為test.txt
task copyFile(type: Copy){
from "src"
into "dst"
}
第一個這里的helloWorld是一個DefaultTask類型的對象,這也是定義一個Task時的默認類型,當然我們也可以顯式地聲明Task的類型,甚至可以自定義一個Task類型。
第二個代碼中(type:Copy)就是“顯式地聲明Task的類型”,執行gradle copyFile
test.txt也跑到dst中去了。
如果task聲明在根Project的build.gradle中的allprojects()方法中,那么這個Task會應用於所有的Project。
task的依賴關系
Gradle不提供內建的項目生命周期管理,只是java Plugin向Project中添加了許多Task,這些Task依次執行,為我們營造了一種如同Maven般項目構建周期。那么這些task是如何依次執行的這就用到聲明的依賴關系taskA.dependsOn taskB
看下面代碼:
task taskA << { println 'this is taskA from project 1' }
task taskB << {
println 'this is taskB from project 1'
}
taskA.dependsOn taskB
然后我們在命令行運行:
$ gradle taskA
運行結果會先執行taskB的打印,然后執行taskA的打印
如果是Muliti-Project的模式,依賴關系要帶着所屬的Project,如
taskA.dependsOn ':other-project:taskC'
其中taskC位於和taskA不同的Project中,相對於AndroidStudio來說,就是位於不同的Module下的build.gradle
中,而other-project為Module名字。
Task 的type可以自定義(沒有深入研究)
自定義Plugin的寫法
沒有深入研究,給出一個網上的例子:
apply plugin: DateAndTimePlugin
dateAndTime {
timeFormat = 'HH:mm:ss.SSS'
dateFormat = 'MM/dd/yyyy'
}class DateAndTimePlugin implements Plugin<Project> {
//該接口定義了一個apply()方法,在該方法中,我們可以操作Project,
//比如向其中加入Task,定義額外的Property等。
void apply(Project project) {
project.extensions.create("dateAndTime", DateAndTimePluginExtension)project<span class="token punctuation">.</span><span class="token function">task</span><span class="token punctuation">(</span><span class="token string">'showTime'</span><span class="token punctuation">)</span> <span class="token operator"><<</span> <span class="token punctuation">{</span> println <span class="token string">"Current time is "</span> <span class="token operator">+</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">format</span><span class="token punctuation">(</span>project<span class="token punctuation">.</span>dateAndTime<span class="token punctuation">.</span>timeFormat<span class="token punctuation">)</span> <span class="token punctuation">}</span> project<span class="token punctuation">.</span>tasks<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token string">'showDate'</span><span class="token punctuation">)</span> <span class="token operator"><<</span> <span class="token punctuation">{</span> println <span class="token string">"Current date is "</span> <span class="token operator">+</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">format</span><span class="token punctuation">(</span>project<span class="token punctuation">.</span>dateAndTime<span class="token punctuation">.</span>dateFormat<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span>
}
//每個Gradle的Project都維護了一個ExtenionContainer,
//我們可以通過project.extentions進行訪問
//比如讀取額外的Property和定義額外的Property等。
//向Project中定義了一個名為dateAndTime的extension
//並向其中加入了2個Property,分別為timeFormat和dateFormat
class DateAndTimePluginExtension {
String timeFormat = "MM/dd/yyyyHH:mm:ss.SSS"
String dateFormat = "yyyy-MM-dd"
}
每一個自定義的Plugin都需要實現Plugin接口,除了給Project編寫Plugin之外,我們還可以為其他Gradle類編寫Plugin。該接口定義了一個apply()方法,在該方法中,我們可以操作Project,比如向其中加入Task,定義額外的Property等。
Gradle Wrapper
Wrapper,顧名思義,其實就是對Gradle的一層包裝,便於在團隊開發過程中統一Gradle構建的版本,然后提交到git上,然后別人可以下載下來,這樣大家都可以使用統一的Gradle版本進行構建,避免因為Gradle版本不統一帶來的不必要的問題。(所以要明白這個東西可以沒有,有了只是為了統一管理,更加方便)
生成wrapper
gradle 內置了生成wrapper的task,我們可以命令行下執行:
$ gradle wrapper
生成后的目錄結構如下(用過AndroidStudio的很熟悉了):
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
└── gradlew.bat
- gradlew和gradlew.bat分別是Linux和Window下的可執行腳本,他們的用法和gradle原生命令是一樣的,gradle怎么用,他們也就可以怎么用。在MacOS下運行
$ ./gradlew myTask
- gradle-wrapper.jar是具體業務邏輯實現的jar包,gradlew最終還是使用java執行的這個jar包來執行相關gradle操作。
- gradle-wrapper.properties是配置文件,用於配置使用哪個版本的gradle等
詳細的看下gradle-wrapper.properties內容
#Sat Jan 21 14:02:40 CST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-bin.zip
從上面內容和文件的名稱都可以看出,這就是個java的配置文件,上面看到的是自動生成的,我們也可以手動修改。然后看下各個字段的含義:
- distributionBase 下載的gradle壓縮包解壓后存儲的主目錄
- distributionPath 相對於distributionBase的解壓后的gradle壓縮包的路徑
- zipStoreBase 同distributionBase,只不過是存放zip壓縮包的
- zipStorePath 同distributionPath,只不過是存放zip壓縮包的
- distributionUrl gradle發行版壓縮包的下載地址,也就是你現在這個項目將要依賴的gradle的版本。
生成wrapper可以指定參數
- 生成wrapper可以通過指定參數的方式來指定gradle-wrapper.properties內容。
- 使用方法如
gradle wrapper –gradle-version 2.14
這樣,這樣就意味着我們配置wrapper使用2.14版本的gradle,它會影響gradle-wrapper.properties中的distributionUrl的值,該值的規則是http://services.gradle.org/distributions/gradle-${gradleVersion}-bin.zip - 如果我們在調用gradle wrapper的時候不添加任何參數呢,那么就會使用你當前Gradle的版本作為生成的wrapper的gradle version。例如你當前安裝的gradle是2.10版本的,那么生成的wrapper也是2.10版本的。注:當前版本指的是環境變量中配置的那個版本。
【參考文章】
http://www.infoq.com/cn/articles/android-in-depth-gradle/
http://blog.csdn.net/innost/article/details/48228651