Gradle基礎

1. 構建文件

在Android構建項目時會自動生成三個gradle文件:setting.gradle和build.gradle以及Android模塊內的build.gradle文件。

1.1 settings文件

settings文件在初始化階段被執行,并且定義了哪些模塊應該包含在構建內,Gradle會為每個settings文件創建一個Settings對象。默認情況下的內容如下:

include ':app'

在上述代碼中,只有app模塊被包含在內。正常來講,單模塊構建不一定需要settings文件,但是多模塊構建一定需要,否則Gradle不知道哪個模塊要包含在構建內。

1.2 頂層構建文件

頂層構建文件即位于根目錄下的build.gradle文件。所有模塊的配置參數都是在頂層的build.gradle文件中配置的。默認配置如下:

buildscript {
    
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.1'
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

實際的構建配置都在buildscript代碼塊內。

  • repositories代碼塊主要用于配置項目依賴倉庫,上述代碼將google,jcenter兩個倉庫配置成倉庫,配置過后我們的應用或者依賴項目就可以下載對應倉庫下的一系列依賴包。

  • dependsncies代碼塊用于配置構建過程中的依賴包。值得注意的是不能將應用或者依賴項目所需要的依賴包包含于頂層構建文件中,默認情況下,唯一被定義的依賴包是Gradle的Android插件。每個Android模塊都需要有Android插件才可以執行Android相關任務。

  • allprojects代碼塊可用來聲明那些需要被用于所有模塊的屬性。

1.3 模塊的構建文件

模塊內部的build.gardle文件的屬性只能應用在Android app模塊,它可以覆蓋頂層build.gradle文件的任何屬性。默認情況下如下:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "com.example.liubohua.gradletest"
        minSdkVersion 15
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
}

總體大致可分為三個部分:插件依賴、Android以及依賴包。

插件依賴

第一行用到了Android應用插件,在頂層構建文件中被配置成了依賴。

Android

引入插件之后,需要對Android進行配置,配置的基本信息如下:

  • compileSdkVersion為必須要有的屬性,用來編譯應用的Android API版本。

  • defaultConfig代碼塊用于配置應用的核心屬性。此段代碼中的屬性可以覆蓋AndroidManifest.xml中的屬性。
defaultConfig {
        applicationId "com.example.liubohua.gradletest"
        minSdkVersion 15
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
  • applicationId覆蓋了manifest文件中得到的packagename。
  • minSdkVersion被用來配置運行應用的最小API級別,targetSdkVersion用于通知系統,該應用已經在某特定Android版本通過測試,從而操作系統不必啟用任何向前兼容的行為。兩種屬性對應manifest的<uses-dsk>中的屬性。
  • versionCode與versitionName表示為你的應用定義一個版本號和版本名稱。同樣會覆蓋manifest當中的屬性。

  • builddTypes代碼塊用來定義如何構建和打包不同構建類型的應用。
依賴包

依賴代碼塊定義了一個應用或依賴項目的所有依賴包。默認情況下會對libs文件夾下的所有jar文件構成依賴。

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}

2. 自定義構建

Gradle構建項目時,可以生成一個BuildGradle類,使用buildConfigField和resValue可以對這個類里面添加自定義的常量,使得在不同的構造模式下會產生不同的效果:

buildTypes {
        debug {
            minifyEnabled false
            buildConfigField("String","TEST_URL","\"http://www.baidu.com\"")
            resValue("string","test_res","debug_res")
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        release {
            minifyEnabled false
            buildConfigField("String","TEST_URL","\"http://www.google.com.hk\"")
            resValue("string","test_res","release_res")
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

這里我們在debug模式和release模式下分別定義了test_res和TEST_URL的不通值,使用在java代碼中的引用如下:

    //debug模式為http://www.baidu.com,release為http://www.google.com.hk
    BuildConfig.TEST_URL
    //debug模式為debug_res,release為release_res
    getResources().getString(R.string.test_res)

對于Project對象我們也可以添加屬性,添加額外屬性需要用到ext代碼塊。
如我們想要定義一個config.build文件來統一管理我們各個moudle的配置,config.gradle的內存如下:

ext {
    compileSdkVersion = 26
    buildToolsVersion = "26.0.2"
}

在頂層的build.gradle當中添加依賴

apply from: 'config.gradle'

buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.1'  
    }
}

在模塊構建文件當中引用

android {
    compileSdkVersion rootProject.ext.compileSdkVersion
    buildToolsVersion rootProject.ext.buildToolsVersion
}

如果不采用config.gradle當中的配置,在模塊構建文件當中可以對屬性重新定義,如果模塊構建文件中定義的屬性在頂層構建文件中已經存在,那么新屬性將覆蓋原來的屬性。

3. 依賴配置

gradle添加依賴的方式主要有一下幾種:

  • compile(implementation或api)
  • apk
  • provided
  • testCompile
  • androidTestCompile
    compile是默認的配置,在編譯主應用時包含所有的依賴。該配置不僅會將依賴添加至類路徑,還會生成對應的APK。

在Android studio 3.0.0中使用了最新的Gralde 4.0 里程碑版本作為gradle的編譯版本。在新版本中compile 指令被標注為過時方法,而新增了兩個依賴指令,一個是implement 和api,兩個新指令用法相同,相比于以前的compile指令,implement指令對于使用了該命令編譯的依賴,對該項目有依賴的項目將無法訪問到使用該命令編譯的依賴中的任何程序,也就是將該依賴隱藏在內部,而不對外部公開。

apk配置使該依賴只會打包到APK,而不會添加到編譯類路徑。provided配置則完全相反,不會被打包到APK。這兩個配置只適用于JAR依賴,如果在依賴項目中添加他們則會導致錯誤。
testCompile和androidTestCompile配置會添加用于測試的額外依賴庫,在運行測試相關任務時會被使用。
除去標準配置外,Android插件還可以針對每個構建變體生成一份配置,其格式如下:buildType+配置類型,例如debugCompile、releaseProvided等。

4. 構建類型

在gradle的Android插件中,每一個模塊都有一個debug構建類型,其被設置為默認構建類型。同時我們可以在buildTypes當中自定義構建類型。

buildTypes {
        debug {
            minifyEnabled false
            buildConfigField("String", "TEST_URL", "\"http://www.baidu.com\"")
            resValue("string", "test_res", "debug_res")
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        lbh {
            minifyEnabled true
            applicationIdSuffix ".lbh"
            versionNameSuffix "-lbh"
            buildConfigField "String", "SYAGINHG_URL", "\"http://www.lbh.com\""
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

我們新定義了lbh構建類型,并為其添加設置(applicationId添加“.lbh”后綴,為versitionName版本名添加-lbh后綴)。這樣在同一個設備當中就可以同事安裝lbh版本和debug兩個版本,從而避免發生沖突。
我們也可以使用已經定義好的構建類型來初始化一個新的構建類型:

buildTypes {
        debug {
            minifyEnabled false
            buildConfigField("String", "TEST_URL", "\"http://www.baidu.com\"")
            resValue("string", "test_res", "debug_res")
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        lbh.initWith(buildTypes.debug)
        lbh {
            minifyEnabled true
            applicationIdSuffix ".lbh"
            versionNameSuffix "-lbh"
            buildConfigField "String", "SYAGINHG_URL", "\"http://www.lbh.com\""
        }
    }

initWith()方法復制了一個已存在的構建類型的所有屬性到新的構建類型當中,當然我們可以根據需要在新的構建類型中對他們重新定義或者添加新的屬性。

5. product flavor

當我們同一個APP需要產出不同版本的應用時,就需要用到product flavor。最典型的例子是一個應用的付費版和免費版。
使用productFlavor代碼塊中添加新的Product flavor來創建。

andrlod{
    flavorDimensions "color","price"

    productFlavors {
        red {
            dimension "color"
            applicationId 'com.example.liubohua.red'
            versionCode 3
        }
        blue {
            dimension "color"
            applicationId 'com.example.liubohua.blue'
            versionCode 4
        }
        free {
            dimension "price"
        }
        paid {
            dimension "price"
        }
    }
}

product flavor可以擁有自己的源集目錄,該文件的名稱格式為flavor名稱+構建類型名稱。
flavorDimensions字段可以為flavor添加維度,flavorDimensions后面參數的順序將決定構建variant的名稱,第一個字段將覆蓋第二個字段。假設構建類型為debug和release,則上述flavor將會生成如下variant:

  • blueFreeDebug和blueFreeRelease
  • bluePaidDebug和bluePaidRelease
  • redFreeDebug和redFreeRelease
  • redPaidDebug和redPaidRelease

6. Groovy

6.1 簡介

Groovy是一種基于JVM(Java虛擬機)的敏捷開發語言,它結合了Python、Ruby和Smalltalk的許多強大的特性,Groovy 代碼能夠與 Java 代碼很好地結合,也能用于擴展現有代碼。由于其運行在 JVM 上的特性,Groovy 可以使用其他 Java 語言編寫的庫。

簡單的配置方法如下(Mac)

vim ~/.bash_profile

在彈框中輸入

export PATH=$PATH:/Users/xuhongchuan/install/groovy-2.4.13/bin

退出保存。

  • 讓修改的設置生效
source ~/.bash_profile
  • 打印版本號確認是否安裝成功
groovy -v

打印的結果為

Groovy Version: 2.4.13 JVM: 1.8.0_141 Vendor: Oracle Corporation OS: Mac OS X

則表示安裝成功。

  • 使用IntelliJ IDEA即可進行開發。

6.2 基本語法

先來看一個簡單的例子

def x = 10;
def s1 = 'hello ${x} world'
println s1
def s2 = "hello ${x} world"
println s2

結果輸出

hello ${x} world
hello 10 world

6.3 定義變量和方法

在groovy中支持動態類型,即不需要指定變量的類型。變量定義可以使用關鍵字def。

def a = 1 //定義一個整形
def b = 'dfdsfa' //定義一個字符串
def double c = 1.1 //定義一個double類型,double可以省略

無返回類型的函數定義,必須使用def關鍵字 ,最后一行代碼的執行結果就是本函數的返回值。

def func(){
    return 8
}
int func2(def a,def b){
    a*b
}
println func() //8
println func2(2,3) //6

定義方法時,return可以省略,如果不使用return,則默認最后一行為返回值。 返回類型可以指定為整形,也可用def,使用def返回類型與最后一行類型相同。

另一種定義方法的方式為:

def func = {num,num2 ->
    num = num+num2 //num=3
    num*num2
}
println(func(1,2)) //6

這種定義方式在Gradle當中很常見。這不是一個常規方法,而是一個closure,即匿名代碼塊,可以接受參數和返回值。
在這種定義中內含了未分類參數it的概念。如果你沒有明確給closure指定參數,則Groovy會自動添加一個參數it,使用方式如下

def func = {
    it * it
}
println(func(8))        //64

6.4 類

在Groovy中定義一個類的方式如下:

class GroovyClass {
    def str
    def getStr() {
        return str
    }
    void setStr(str) {
        this.str = str
    }
}

定義方式與java中的定義方式類似,但是在沒有修飾符修飾的情況下。Groovy中的默認訪問修飾符與Java不同。類和方法一樣是公有的,但是成員變量是私有的。GroovyClass的使用方式與java類似。

def testClass = new GroovyClass()
testClass.setStr('hello world')
println(testClass.str)//hello world
println(testClass.getStr())  //hello world

當你調用一個變量時,實際調用的是該變量的getter方法,所以后兩種方式打印的結果完全相同。

6.5 Groovy容器

這里介紹兩種Groovy容器:List和Map

  • List:其底層對應Java中的List接口。
List list = [1,2,3,4]
println list[0]
list[2] = 5
list<<6
list.each {elements->
    println(elements)
}

上述代碼初始化了一個List,對list[0]單獨進行輸出之后對list[2]進行賦值,通過“list<< 6”語句向list集合中添加元素,之后對List進行遍歷并對元素進行輸出。
輸出結果為

1
1
2
5
4
6

遍歷時也可以使用it變量。

List list = [1,2,3,4];
list.each {
    println(it)
}
  • map:鍵-值表
def map = [key1: "value1", key2: "value2", key3: "value3"]
println map
println(map.keySet())
println(map.values())
println("key1的值:" + map.key1)
println("key1的值:" + map.get("key1"))
map.put("key4", "value4")

Iterator it = map.iterator()
while (it.hasNext()) {
    println "遍歷map: " + it.next()
}
  1. 定義一個map集合
  2. 輸出map集合
  3. 輸出map集合的key所組成的集合
  4. 輸出所有value值得集合
  5. 輸出集合中key1所對應的值
  6. 輸出集合中key1所對應的值
  7. 對key4進行賦值并加入map集合當中
  8. 遍歷輸出map集合
    上述代碼輸出如下
[key1:value1, key2:value2, key3:value3]
[key1, key2, key3]
[value1, value2, value3]
key1的值:value1
key1的值:value1
遍歷map: key1=value1
遍歷map: key2=value2
遍歷map: key3=value3
遍歷map: key4=value4

7. 任務

Gradle在構建過程中一共可以分為三個階段:初始化階段、配置階段和執行階段。當我們使用task創建完一個任務時,需要添加“<<”告知Gradle代碼塊會在執行階段執行,而不是配置階段。

task hello << {
    println('hello world')
}

輸出結果為:

> Task :hello
hello world

BUILD SUCCESSFUL in 1s

實際上想要在執行階段執行代碼,需要使用doFirst()或者doLast()來為一個task添加代碼。我們之前用來定義task說的左移運算符(<<)就是doFirst()得縮寫。

task hello  {
    println('hello world')
    doFirst {
        println 'Hello'
    }
    doLast {
        println 'hello again'
    }
    doLast {
        println 'Goodbye'
    }
}

輸出結果

> Configure project :
hello world

> Task :hello
Hello
hello again
Goodbye

值得注意得是doFirst必須總是第一個添加到task當中,后續的doLast會依次順序執行。
還有兩個方法在構建時會經常用到。mustRunAfter()以及dependsOn()

  • mustRunAfter():用于對task進行排序,當定義該方法后,如果涉及到的兩個任務都被執行,無論定義順序如何,執行順序都會按照之前定義好的執行。
task task1 << {
    println 'test1'
}
task task2 << {
    println 'test2'
}
task2.mustRunAfter task

執行./gradlew task1 task2或者./gradlew task2 task1輸出結果相同

> Task :task1
test1
> Task :task2
test2
  • dependsOn():定義一個任務必須依賴于另一個任務。
task task1 << {
    println 'test1'
}
task task2 << {
    println 'test2'
}
task2.dependsOn task1

執行./gradlew task2直接輸出結果

> Task :task1
test1
> Task :task2
test2

相比于mustRunAfter,在未聲明task1的時候執行task2也會出發task1。

8 尾聲

剛剛接觸簡書,本人是17年畢業的萌新,最近學習了一點關于gardle的知識寫筆記記錄一下,如果有什么錯誤的地方還請大神多多指正。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,237評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,957評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,248評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,356評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,081評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,485評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,534評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,720評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,263評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,025評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,204評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,787評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,461評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,874評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,105評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,945評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,205評論 2 375

推薦閱讀更多精彩內容