1. 程式人生 > >理解與配置Android studio中的gradle

理解與配置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的探討。