這段時間來學習了gradle,也體會到了gradle從初步理解到基本熟悉,再到深刻源碼這樣一個過程當中的一些曲折。因而就萌發了寫這樣一篇逐步深刻原理的文章的想法。html
這篇文章主要是gradle的基礎知識篇。看完這篇文章,你能夠:java
若是你想關注gradle更深刻的一些知識,請繼續關注後續gradle文章。python
先來看一段維基百科上對於gradle的解釋。android
Gradle是一個基於Apache Ant和Apache Maven概念的項目自動化構建工具。它使用一種基於Groovy的特定領域語言來聲明項目設置,而不是傳統的XML。當前其支持的語言限於Java、Groovy和Scala,計劃將來將支持更多的語言。shell
可能剛接觸gradle的同窗都不是很瞭解gradle的這個定義。可能就只會跟着網上的教程copy一點配置,可是不理解這些配置背後的原理。那麼怎麼來理解這句話呢,咱們能夠把握到三個要點:首先,它是一種構建工具
,其次,gradle是基於maven概念
的,最後,使用groovy
這種語言來聲明。要理解這幾句話,咱們先考慮幾個場景。緩存
1.渠道管理
:國內手機市場有大大小小數十個,大的手機廠商也有五六個,每一個廠商可能又有不一樣的定製rom。若是咱們要爲不一樣市場和廠商進行適配,那就須要寫這樣的代碼安全
if(isHuawei) {
// dosomething
} else if(isOppo) {
// dosomething
}
複製代碼
這樣的話,繁瑣不說,對單個手機而言大量的無用代碼被編譯進apk中,包體積和運行速度都會受影響。爲了解決這個問題,gradle引進了productFlavor和buildType的能力,能根據狀況來進行打包。因此說他是一個自動化構建工具
。能夠看官方文檔bash
2.依賴管理
:咱們一般會在項目中引入各類三方庫進行代碼複用。好比,直接手動把jar或者aar copy到項目中,而後添加依賴。這種方法缺陷很明顯,首先配置和刪除流程很繁瑣,其次,同一個jar可能會被多個項目所引用,致使不知不覺就copy了多個jar。最後,版本管理艱難。爲了解決這個問題,gradle是基於maven倉庫,配置和刪除的時候僅須要對倉庫的座標進行操做,全部的庫都會被gradle統一管理,大多數狀況下每一個庫只會有一個版本存在於項目中,而且每一個庫只會有一個副本存在於項目中。閉包
因此gradle其實不是什麼神祕的東西,只是基於某種語言(groovy, java, kotlin)的一種構建工具而已。只要咱們大概掌握了基本的用法和他的內部原理,平常工做中就會知道本身網上搜到的命令是什麼意思啦。skr~app
我們先看看平常工做中常常用到的幾個gradle文件。能夠看到主要有有三個文件: 1.build.gradle 根文件下放的一般放的是針對整個工程的通用配置,每一個module下面的build.gradle文件是針對每一個module自身的配置。
buildscript {
ext.kotlin_version = '1.2.71'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
jcenter()
}
}
複製代碼
這是一個默認的配置,咱們能夠看到有buildscript,allprojects,repositories,dependencies幾個配置項,這些配置項是幹嗎的呢,不少的同窗在剛學gradle的時候都是一臉懵逼的。這些實際上是gradle的一種特定的語法,咱們稱之爲DSL(domain-specific language)。能夠參考官網。這裏能夠看到allprojects代理的是每一個project,能夠理解成咱們的每一個module,也就是對咱們所寫的每一個module的配置。buildscript主要配置的是打包相關的東西,好比gradle版本,gradle插件版本等,這些都是針對構建工具本身的配置。repositories,dependencies是三方庫的倉庫和座標。因此根目錄的build.gradle至關因而總體的配置。
而module下的build.gradle主要是android,dependencies等配置項。
apply plugin: 'com.android.application'
android{
...
}
dependencies{
...
}
複製代碼
可能有些同窗會感到奇怪,爲啥咱們在官網沒有看到android這個配置項呢?這個主要是由於它並非gradle的DSL,某種意義上說應該算是android特有的,是經過Android的插件'com.android.application'帶進來的配置項。咱們若是把第一行刪掉,就會發現android{}這個配置項找不到了。
因此,咱們能夠發現,build.gradle裏面的配置項,要麼是gradle自帶的,要麼是各類插件定義的。有不認識的配置項,就去官網查詢一下就行了,授人以魚不如授人以漁嘛。咱們後面也會講解到引進插件的方式和怎麼定義插件和配置項。
2.settings.gradle 這個文件主要是決定每一個module是否參與構建。咱們能夠這樣去理解,settings.gradle至關因而每一個module的開關,關上了這個module就不能使用了,別的依賴到它的module也都會出問題。
3.gradle.properties 這裏主要是增長和修改一些能夠在構建過程當中直接使用的參數。不僅是能夠添加自定義參數,還能夠修改系統的參數哦~
總結一下,就是說根目錄下有一個build.gradle,處理整個工程的配置項,根目錄下的settings.gradle配置整個工程中參與構建的module,每一個module本身有一個build.gradle,處理本身模塊的配置。這就是android構建的一個大概狀況。固然,看了這一部分確定仍是不懂怎麼去寫的,接下來咱們走進代碼層面。
gradle可使用groovy,kotlin,java等語言進行書寫,可是groovy相對來講是目前比較流行的gradle配置方式,下面咱們講解一點groovy基礎。不講太多,夠用就行。
1.字符串
groovy的字符串分爲兩種java.lang.String和groovy.lang.GString。其中單引號和三引號是String類型的。雙引號是GString類型的。支持佔位插值操做。和kotlin同樣,groovy的插值操做也是用${}
或者$
來標示,${}
用於通常替代字串或者表達式,$
主要用於A.B的形式中。
def number = 1
def eagerGString = "value == ${number}"
def lazyGString = "value == ${ -> number }"
println eagerGString
println lazyGString
number = 2
println eagerGString
println lazyGString
複製代碼
2.字符Character
Groovy沒有明確的Character。可是能夠強行聲明。
char c1 = 'A'
assert c1 instanceof Character
def c2 = 'B' as char
assert c2 instanceof Character
def c3 = (char)'C'
assert c3 instanceof Character
複製代碼
4.List
Groovy的列表和python的很像。支持動態擴展,支持放置多種數據。使用方法支持def和直接定義。還能夠像python那樣索引
//List中存儲任意類型
def heterogeneous = [1, "a", true]
//判斷List默認類型
def arrayList = [1, 2, 3]
assert arrayList instanceof java.util.ArrayList
//使用as強轉類型
def linkedList = [2, 3, 4] as LinkedList
assert linkedList instanceof java.util.LinkedList
//定義指定類型List
LinkedList otherLinked = [3, 4, 5]
assert otherLinked instanceof java.util.LinkedList
// 像python同樣索引
assert letters[1] == 'b'
//負數下標則從右向左index
assert letters[-1] == 'd'
assert letters[-2] == 'c'
//指定item賦值判斷
letters[2] = 'C'
assert letters[2] == 'C'
//給List追加item
letters << 'e'
assert letters[ 4] == 'e'
assert letters[-1] == 'e'
//獲取一段List子集
assert letters[1, 3] == ['b', 'd']
assert letters[2..4] == ['C', 'd', 'e']
複製代碼
5.Map
//定義一個Map
def colors = [red: '#FF0000', green: '#00FF00', blue: '#0000FF']
//獲取一些指定key的value進行判斷操做
assert colors['red'] == '#FF0000'
assert colors.green == '#00FF00'
複製代碼
6.運算符
*.
展開運算符,一個集合使用展開運算符能夠獲得一個元素爲原集合各個元素執行後面指定方法所得值的集合。cars = [
new Car(make: 'Peugeot', model: '508'),
null,
new Car(make: 'Renault', model: 'Clio')]
assert cars*.make == ['Peugeot', null, 'Renault']
assert null*.make == null
複製代碼
7.閉包 groovy裏比較重要的是閉包的概念。官方定義是「Groovy中的閉包是一個開放,匿名的代碼塊,能夠接受參數,返回值並分配給變量」。 其實閉包跟kotlin的lambda函數很像,都是先定義後執行。可是又有一些細微的區別。接下來咱們細講講gradle的閉包。
閉包是能夠用做方法參數的代碼塊,Groovy的閉包更象是一個代碼塊或者方法指針,代碼在某處被定義而後在其後的調用處執行。一個閉包實際上就是一個Closure類型的實例。寫法和kotlin的lambda函數很像。
咱們常見的閉包是這樣的
//最基本的閉包
{ item++ }
//使用->將參數與代碼分離
{item -> item++ }
//使用隱含參數it
{ println it }
//使用顯示的名爲參數
{ name -> println name }
// 調用方法
a.call()
a()
// Groovy的閉包支持最後一個參數爲不定長可變長度的參數。
def multiConcat = { int n, String... args ->
args.join('')*n
}
複製代碼
你們要注意,若是咱們單純的只是寫成 a = { item++ }, 這只是定義了一個閉包,是不能運行的。必須調用a.call()才能運行出來。因此你們能夠理解了,閉包就是一段代碼塊而已。當咱們有須要的時候,能夠去運行它,這麼一想是否是和lambda函數很像?
若是你看了官網,你會發現有一些這樣的說法,
什麼叫作delegate?這裏涉及到閉包內部的三種對象。
this和owner都比較好理解。咱們能夠用閉包的getxxx方法獲取
def thisObject = closure.getThisObject()
def ownerObject = closure.getOwner()
def delegate = closure.getDelegate()
複製代碼
重頭戲仍是delegate這個對象。閉包能夠設置delegate對象,設置delegate的意義就是將閉包和一個具體的對象關聯起來。 咱們先來看個例子,這裏以自定義android閉包爲例。
android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
minSdkVersion 15
targetSdkVersion 25
versionCode 1
versionName "1.0"
}
}
複製代碼
這個閉包對應的實體類是兩個。
# Android.groovy
class Android {
private int mCompileSdkVersion
private String mBuildToolsVersion
private ProductFlavor mProductFlavor
Android() {
this.mProductFlavor = new ProductFlavor()
}
void compileSdkVersion(int compileSdkVersion) {
this.mCompileSdkVersion = compileSdkVersion
}
void buildToolsVersion(String buildToolsVersion) {
this.mBuildToolsVersion = buildToolsVersion
}
void defaultConfig(Closure closure) {
closure.setDelegate(mProductFlavor)
closure.setResolveStrategy(Closure.DELEGATE_FIRST)
closure.call()
}
@Override
String toString() {
return "Android{" +
"mCompileSdkVersion=" + mCompileSdkVersion +
", mBuildToolsVersion='" + mBuildToolsVersion + '\'' + ", mProductFlavor=" + mProductFlavor + '}' } } # ProductFlavor.groovy class ProductFlavor { private int mVersionCode private String mVersionName private int mMinSdkVersion private int mTargetSdkVersion def versionCode(int versionCode) { mVersionCode = versionCode } def versionName(String versionName) { mVersionName = versionName } def minSdkVersion(int minSdkVersion) { mMinSdkVersion = minSdkVersion } def targetSdkVersion(int targetSdkVersion) { mTargetSdkVersion = targetSdkVersion } @Override String toString() { return "ProductFlavor{" + "mVersionCode=" + mVersionCode + ", mVersionName='" + mVersionName + '\'' + ", mMinSdkVersion=" + mMinSdkVersion + ", mTargetSdkVersion=" + mTargetSdkVersion + '}' } } 複製代碼
而後定義的時候就寫成
//閉包定義
def android = {
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
minSdkVersion 15
targetSdkVersion 25
versionCode 1
versionName "1.0"
}
}
//調用
Android bean = new Android()
android.delegate = bean
android.call()
println bean.toString()
//打印結果
Android{mCompileSdkVersion=25, mBuildToolsVersion='25.0.2', mProductFlavor=ProductFlavor{mVersionCode=1, mVersionName='1.0', mMinSdkVersion=15, mTargetSdkVersion=25}}
複製代碼
這樣就能將閉包中聲明的值,賦給兩個對象Android和ProductFlavor來處理了。
上面官網的圖裏,說ScriptHandler被設置成buildscript的delegate。意思就是buildscript定義的參數被ScriptHandler拿來使用了。你們有興趣的能夠去看看ScriptHandler的源碼~
上面咱們講完了基本的用法,你們可能懂gradle的配置和寫法了。可是可能仍是不懂gradle的構建體系究竟是怎麼樣的。這裏咱們就要深刻進gradle的構建體系Project和Task了。下面的東西看着就要動動腦筋了。
1.Task Task是gradle腳本中的最小可執行單元。類圖以下:
值得注意的是由於Gradle構建腳本默認的名字是build.gradle,當在shell中執行gradle命令時,Gradle會去當前目錄下尋找名字是build.gradle的文件。因此只有定義在build.gradle中的Task纔是有效的。
能夠經過三種方式來聲明task。咱們能夠根據本身的項目須要去定義Task。好比自定義task接管gradle的編譯過程
task myTask2 << {
println "doLast in task2"
}
//採用 Project.task(String name) 方法來建立
project.task("myTask3").doLast {
println "doLast in task3"
}
//採用 TaskContainer.create(String name) 方法來建立
project.tasks.create("myTask4").doLast {
println "doLast in task4"
}
複製代碼
TaskContianer 是用來管理全部的 Task 實例集合的,能夠經過 Project.getTasks() 來獲取 TaskContainer 實例。 常見接口:
findByPath(path: String): Task getByPath(path: String): Task getByName(name: String): Task withType(type: Class): TaskCollection matching(condition: Closure): TaskCollection //建立task create(name: String): Task create(name: String, configure: Closure): Task create(name: String, type: Class): Task create(options: Map<String, ?>): Task create(options: Map<String, ?>, configure: Closure): Task //當task被加入到TaskContainer時的監聽 whenTaskAdded(action: Closure) 複製代碼
Gradle支持增量編譯。瞭解過編譯profile文件的朋友都知道,裏面有大量的task都是up-to-date
。那麼這種up-to-date是什麼意思呢。Gradle的Task會把每次運行的結果緩存下來,當下次運行時,會檢查一個task的輸入輸出有沒有變動。若是沒有變動就是up-to-date,跳過編譯。
2.Project 先從Project對象講起,Project是與Gradle交互的主接口。android開發中最爲咱們所熟悉的就是build.gradle文件,這個文件與Project是一對一的關係,build.gradle文件是project對象的委託,腳本中的配置都是對應着Project的Api。Gradle構建進程啓動的時候會根據build.gradle去實例化Project類。也就是說,構建的時候,每一個build.gradle文件會生成一個Project對象,這個對象負責當前module的構建。
Project本質上是包含多個Task的容器,全部的Task存在TaskContainer中。咱們從名字能夠看出
能夠看到dependencies, configuration, allprojects, subprojects, beforeEvaluate, afterEvaluate這些都是咱們常見的配置項,在build.gradle文件中接收一個閉包Closure。
好了,如今咱們已經聊了build.gradle了,可是你們都知道,咱們項目中還有一個settings.gradle呢,這個是拿來幹嗎的呢?這就要說到Project的Lifecycle
了,也就是Gradle構建Project的步驟,看官網原文:
Project.evaluationDependsOnChildren()
or by adding an explicit evaluation dependency using Project.evaluationDependsOn(java.lang.String)
.也就是說,Project對象依賴Settings對象的構建。咱們常在settings.gradle文件中配置須要引入的module,就是這個緣由。
3.Property 看完了build.gradle和settings.gradle,接下來咱們講講gradle.properties。這個文件存放的鍵值對形式的屬性,這些屬性能被項目中的gradle腳本使用ext.xxx所訪問。
咱們也可使用Properties類來動態建立屬性文件。如:
def defaultProps = new Properties()
defaultProps.setProperty("debuggable", 'true')
defaultProps.setProperty("groupId", GROUP)
複製代碼
而且屬性能夠繼承,在一個項目中定義的屬性能夠自動被子項目繼承。因此在哪一個子項目均可以使用project.ext.xxx訪問。不一樣子項目間採用通用的配置插件來配置
apply from: rootProject.file('library.gradle')
複製代碼
經過上面的學習,你們應該已經瞭解了gradle的基本配置,寫法和比較淺顯的內部原理了。由於篇幅緣由,深刻的內容咱們放在下一篇。敬請期待《一篇文章深刻gradle》
我是Android笨鳥之旅,一個陪着你慢慢變強的公衆號。