使用gradle構建android應用時,你總是需要這樣一個文件:build.gradle。你應該已經看過這個文件了,如果沒有看過的話,你現在就可以看一下,它沒有多少內容。它的簡潔性得益於它提供了很多對設置和屬性的默認值。gradle是基於groovy語言的,不過如果只是用它構建普通的工程的話,是可以不去學groovy的,如果想深入的做一下自定義的構建插件,可以考慮學一下groovy,因爲它是基於java的,所以你有java基礎的話,學習不會很難。
這篇博客旨讓任何一個人能看懂android studio的gradle scripts,主要會從gradle的簡單語法,gradle scripts的腳本結構,每一個腳本(build.gradle,settings.gradle)的作用,腳本中每一項的意義等方面說明gradle scripts.如果想了解如何詳細配置gradle,比如實現一個工程中,使用同一部分java代碼,不同的資源res,一次生成多個不同渠道商的apk,可以看下我的這篇博客,它對如何配置gradle有較細緻的介紹:詳細配置android studio中的gradle
1.projects , tasks and action
是的,工程,任務和行爲。一個項目至少要有一個工程,一個工程至少要有一個任務,一個任務由一些action組成。如果project比較抽象的話,可以這麼理解,一個build.gradle對應一個project,而action就好像java中的方法,他就是一段代碼的集合。在工程構建的過程中,gradle會根據build.gradle中的配置信息生成相應的project和task。
Project實質上是一系列task的集合,每一個task執行一些工作,比如編譯類文件,解壓縮文件,刪除文件等等。
1.1構建過程
1.1.1初始化階段。首先會創建一個Project對象,然後執行build.gradle配置這個對象。如果一個工程中有多個module,那麼意味着會有多個Project,也就需要多個build.gradle.
1.1.2配置階段。這個階段,配置腳本會被執行,執行的過程中,新的task會被創建並且配置給Project對象。
1.1.3執行階段。這個階段,配置階段創建的task會被執行,執行的順序取決於啓動腳本時傳入的參數和當前目錄。
1.2 task
task標示一個邏輯上的執行單元,你可能已經用過它很多次了,不知道你有沒有意識到。當你當你重新編譯工程的時候,會用到一個叫做build 的task,當你清理工程的時候,會用到一個叫做clean 的task(後面會講到),gradle 已經爲你準備了很多的task,可以使用 gradle tasks 來查看,比如,這裏列出來一些:
assemble - Assembles all variants of all applications and secondary packages.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
clean - Deletes the build directory.
此外,你還可以自己聲明一個task,比如像這樣:
task haha {
println "haha"
}
然後使用gradle haha命令,就會打印出haha。這裏,haha這個任務被執行了,所以說task就是個執行單元。你還可以使用如下方法來定義task:
task hello << {
println "hello world"
}
這和前者是有區別的,“<<”意思是給hello這個task添加一些action,其實就是調用了task的doLast方法,所以,它和以下代碼時等價的:
task hello {
doLast{
println "hello world"
}
}
關於haha 和 hello的區別,你還可以這樣加深影響:
首先,進入到你的工程目錄,執行gradle (後面沒有任何參數,另外,這個時候,build.gradle中同時有hello 和 haha 兩個tasks),結果如下:
E:\android\androidwork2.0\GradleTest>gradle
haha
Incremental java compilation is an incubating feature.
:help
Welcome to Gradle 2.13.
To run a build, run gradle <task> ...
To see a list of available tasks, run gradle tasks
To see a list of command-line options, run gradle --help
To see more detail about a task, run gradle help --task <task>
BUILD SUCCESSFUL
Total time: 21.877 secs
E:\android\androidwork2.0\GradleTest>gradle tasks
可以按到haha,被打印了,而hello沒有被打印,注意,這個時候默認執行的task 是help,也就是說並沒有執行haha 這個task,可它還是被打印了,就說明使用定義haha 這種方式定義的task在初始化階段就會被執行,而使用定義hello這種方法定義的task在執行階段纔會被執行。
在android studio的頂層build.gradle中有這樣一個task:
task clean(type: Delete) {
delete rootProject.buildDir
}
可以看到這個task有一個類型type,task有很多種類型的,以下列出來一些:
這裏用到了delete類型,task的類型可以這樣理解吧:task中文就是任務,任務有很多種類,Delete就是說這是個刪除文件的任務。
這裏就不更深入的探討task了,這些類容已經可以使我們可以理解android studio中遇到的內容了。
2.Closures
2.1 定義閉包
理解gradle需要首先理解閉包的概念,Closure就是一段代碼塊,代碼塊一般要用{}包起來,所以閉包的定義可以向以下的樣子:
def haha = { println 'haha!' }
haha()
#output:haha!
可以看到閉包雖然可以認爲是一段代碼塊,但它可以向函數一樣調用,而且它還可以接受參數,比如像下面這樣:
def myClosure = {String str -> println str }
myClosure('haha!')
#output: haha!
這樣這個閉包就有參數了,多個參數只需要在->前面添加就好了。
2.2 委託
另外一個很酷的點是closure的上下文是可以改變的,通過Closure#setDelegate()。這個特性非常有用:
def myClosure = {println myVar} //I'm referencing myVar from MyClass class
MyClass hello = new MyClass()
myClosure.setDelegate(hello)
myClosure()
class MyClass {
def myVar = 'Hello from MyClass!'
}
#output: Hello from MyClass!
如上所示,創建closure的時候,myVar並不存在。但是沒關係,因爲當執行closure的時候,在closure的上下文中,myVar是存在的。這個例子中。因爲在執行closure之前改變了它的上下文爲hello,因此myVar是存在的。
2.3閉包作爲參數
閉包是可以作爲參數的傳遞的,以下是閉包作爲參數的一些情況:
1.只接收一個參數,且參數是closure的方法: myMethod(myClosure)
2.如果方法只接收一個參數,括號可以省略: myMethod myClosure
3.可以使用內聯的closure: myMethod {println ‘Hello World’}
4.接收兩個參數的方法: myMethod(arg1, myClosure)
5.和4類似,單數closure是內聯的: myMethod(arg1, { println ‘Hello World’ })
6.如果最後一個參數是closure,它可以從小括號從拿出來: myMethod(arg1) { println ‘Hello World’ }
3.gradle DSL
DSL(Domain Specific Language),中文意思是特定領域的語言。gradle DSL就是gradle領域的語言。爲了更好理解gradle,學習gradle DSL是有必要的。gradle的腳本雖然非常簡短,但它有它的語法,如果不搞懂DSL,即便你知道了怎麼修改腳本得到你想要的結果,你也不會理解爲什麼要這樣修改。
3.1 你必須知道的基本概念
第一. gradle script是配置腳本,當腳本被執行的時候,它配置一個特定的對象。比如說,在android studio工程中,build.gradle被執行的時候,它會配置一個Project對象,settings.gradle被執行時,它配置一個Settings對象。Project,Settings這種對象就叫做委託對象,下圖展示了不同腳本對應的不同的委託對象:
第二.每一個Gradle script實現了一個Script接口,這意味着Script接口中定義的方法和屬性都可以在腳本中使用。
3.2構建腳本的結構
一個構建腳本由零個或多個statements和 script blocks組成。以下是對他們的說明,爲了避免翻譯錯誤,這裏把原文貼出來。
A build script is made up of zero or more statements and script blocks. Statements can include method calls, property assignments, and local variable definitions. 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. The top level script blocks are listed below.
大概意思statments可以包括方法調用,屬性分配,本地變量定義;script bolck則是一個方法,它的參數可以是一個閉包。這個閉包是一個配置閉包,因爲當它被執行的時候,它用來配置委託對象。以android studio的build.gradle爲例:
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.3"
defaultConfig {
applicationId "com.konka.gradletest"
minSdkVersion 16
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}
apply plugin: 'com.android.application'
以上就是一條statements,其中apply 是一個方法,後面是它的參數。這行語句之所以比較難理解是因爲它使用了縮寫,寫全應該是這樣的:
project.apply([plugin: 'com.android.application'])
這樣是不是就很清楚了?project調用了apply方法,傳入了一個Map作爲參數,這個Map的key是plugin,值是com.android.application.
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}
它以上就是一條script block,但它卻很難被理解,之所以這麼難理解,是因爲gradle語法中用了大量的簡寫,dependencies寫完整應該是這樣的:
project.dependencies({
add('compile', 'com.android.tools.build:gradle:2.0.', {
// Configuration statements
})
})
我們知道block是一個閉包,這裏首先調用project下的dependencies方法,這個方法的參數是一個閉包,這個閉包被傳遞給DependencyHandler,DependencyHandler有一個方法:add,這個add有三個參數,分別是'compile','...'和一個閉包。
gradle中有以下頂層build script block:
這裏再以allprojects{ }爲例,說一下script block是怎麼工作的:
allprojects {
repositories {
jcenter()
}
}
allprojects{ }一般是頂層build.gradle中的一個script block,它就是一個方法,這個方法接受一個閉包作爲參數。gradle工具會先創建一個Project對象,它是一個委託對象(delegate object),它創建以後,build.gradle被執行,執行的過程中,allproject{ }方法被調用,這個方法的參數是一個閉包,然後閉包會被執行,用來配置Project對象。
4.Understanding the Gradle files
理解了Project,task和action的概念以後,就可以就理解gradle的配置文件了。在android studio的工程中一般會有三個配置文件,它們各有各的功能。這三個文件的位置應該是這樣的:
構建一個工程的時候,會有以下順序:
1.創建一個Settings對象。
2.檢查settings.gradle是否存在,不存在就什麼都不做,存在就用它來配置Settings對象。
3.使用Settings對象創建Project對象,多Module工程中,會創建一系列的Project.
4.檢查build.gradle是不是存在,存在的話就用它來配置Project對象。
4.1 settings.gradle
如果一個新的工程只包含一個android app,那麼settings.gradle應該是這樣的:
include ':app'
如果你的工程裏只有一個 app,那麼settings.gradle文件可以不要。include ':app'中的app指明你要構建的模塊名,android studio默認的模塊名師app,你可以把app目錄的名字改掉,比如改成hello,那麼這個時候你就必須把settings.gradle中的app也改成hello。這會是你非常有意義的一次嘗試,因爲有了這次嘗試,以後你就可以按你所願修改這個文件了。比如就像這樣修改:
那麼這個時候你肯定已經想試試一次性構建多個app了吧?你以前如果做過,那麼你很厲害,你就不用看了,如果你沒有試過,那麼就和我一起試試吧:
第一步:在你的工程上右鍵,選擇新建mudole。
第二步:你成功了!
是的就這麼簡單,現在看看工程的樣子:
是的,這個時候,settings.gradle中多了一項,他就是我們新加的module的名字,它其實就是工程頂層目錄下的一個目錄的名字。這個名字你可以隨便改,module你也可以隨便加。
注意:settings.gradle實在初始化階段被讀入的,讀入以後會生成一個Settings對象,然後會調用這個對象的一些方法。你沒有必要了解這個對象,你知道它的存在對你理解項目構建的過程有所幫助。
4.2 The top-level build file
就是頂層的build.gradle腳本。這個文件中配置內容將會應用到所有modules中(上一步我們已經創建了兩個module了,一個hello,一個gradletest2)。所以,每個module中都有的共同的屬性,都會在頂層的build.gradle中配置,它默認有以下內容:
<pre style="font-family: 宋體; font-size: 9pt; background-color: rgb(255, 255, 255);"><pre name="code" class="java">buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.0.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
這個腳本是由buildscript {},allprojects{} 兩個script block組成,buildsctipt是一個頂層的build script block,正如2.2中所說的那樣,是一個方法,參數是一個閉包,這個閉包裏面又有一些script block,這些script bolck也是方法,參數也是一個閉包。最終這些閉包會被執行,用來配置對應的委託對象。比如,repositories這個方法的閉包調用了jcenter方法,這個方法會配置gradle的遠程倉庫,配置好了以後,在工程構建過程中,如果缺少依賴,就會在遠程倉庫中查找。頂層build.gradle中的配置會應用到所有的工程中,頂層build.gradle的委託對象是root Project,子工程目錄下的build.gradle對應它自己的Project,總之,一個build.gradle對應一個Project。
至於每個script block的意義,但從字面意思上就能猜出一些來,比如allprojects {}就是爲所有的project配置閉包中的內容,這裏就是配置遠程倉庫,倉庫有很多種,想使用其他倉庫就可以在這裏修改。buildsctipt{}爲所有project配置構建用的倉庫的工具,它裏面的dependecbies{}就是配置構建工具的信息,從中可以看到構建工具是gradle,版本是2.0.0;所以,修改gradle的版本就可以在這裏改。不過單從名字得到的信息是遠遠不夠的,爲了獲取更多的信息,你可以看看《gradle for android》這本書。
4.3子工程下的build.gradle
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.3"
defaultConfig {
applicationId "com.konka.gradletest"
minSdkVersion 16
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.3.0'
}
4.3.1第一行是一個statement,調用了apply方法,這個方法的定義如下:
void apply(Map<String, ?> options);
它的作用是檢查gradle有沒有所聲明的這個插件,有就什麼都不做,沒有的話就會使插件可用。
具體的每一個script block,它們大部分都是都是方法,都可以在android studio 中按住ctrl+鼠標左鍵,點進去看它的聲明,每個方法都有註釋來解釋它的作用。
4.3.1 android block
android 是這個腳本中最大的塊,它包含了andoird特有的插件,這些插件可以使用是因爲之前調用了
apply plugin: 'com.android.application',
此外,這裏設置了編譯android用的參數,構建類型等。
4.3.2 dependencies block
dependecies也是一個接受閉包作爲參數的方法,這裏設置了編譯當前的app的依賴。如果當前app依賴外部的包,可以把這個包放到libs目錄下面,然後右鍵,選擇add as library,然後就會在這裏生成一條compile ' ... '的記錄。
4.3.3還有其他的一些配置,比如:
//這個是解決lint報錯的代碼
lintOptions {
abortOnError false
}
//<span style="font-family: Arial, Helvetica, sans-serif; font-size: 9pt;">簽名設置</span>
signingConfigs {
myConfigs {
storeFile file("簽名文件地址")
keyAlias "..."
keyPassword "..."
storePassword "..."
}
}
//<span style="font-family: Arial, Helvetica, sans-serif; font-size: 9pt;">混淆設置</span>
buildTypes {
release {
signingConfig signingConfigs.myConfigs
runProguard true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
//<span style="font-family: Arial, Helvetica, sans-serif; font-size: 9pt;">渠道打包(不同包名)</span>
productFlavors {
aaa{
applicationId = '包名'
}
bbb{
applicationId='包名'
}
}
}
//<span style="font-family: Arial, Helvetica, sans-serif; font-size: 9pt;">so文件的導入</span>
task copyNativeLibs(type: Copy) {
from fileTree(dir: 'libs', include: 'armeabi/*.so') into 'build/lib'
}
總結:以上的所有內容展示了一個gradle工作的大致過程,gradle腳本的組成方式,概括性的介紹了android studio中每個gradle配置腳本的功能,大概的闡述了一些script block的作用。由於這篇博客旨在理解android studio的gradle的工作方式和腳本的做成結構,所以,如果想更詳細的理解每一個script block的作用,可以看下《gradle for android》這本書。此外,後續的文章也會有詳細的對常用script block的探討。