理解與配置Android studio中的gradle

使用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的探討。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章