Android Gradle 入門指南

在 Android Studio 構(gòu)建的項目中,基于 Gradle 進(jìn)行項目的構(gòu)建肚医,同時使用 Android DSL 進(jìn)行 Android 項目的配置分尸,而 Gradle 是基于 Groovy 腳本語言進(jìn)行開發(fā)姜挺,所以熟練掌握 Android Studio 中的項目配置兼贸,就需要掌握以下知識點:

  • Groovy 基礎(chǔ)
  • Gradle DSL 語法
  • Android DSL 語法

1. Groovy 基礎(chǔ)

Groovy 和 Java 一樣也是一門 JVM 語言段直,最終都會編譯成 .class 文件然后運行在 JVM 上,Groovy 語言類似腳本溶诞,語法簡單更靈活,所以在編寫項目腳本構(gòu)建上優(yōu)勢更加明顯决侈。

1.1 Groovy 環(huán)境配置

工欲善其事必先利其器螺垢,編碼之前環(huán)境先行。網(wǎng)上有很多配置 Groovy 開發(fā)的環(huán)境赖歌,針對 Android 程序員來說枉圃,使用我們的 Android Studio 就能滿足。

第一步:創(chuàng)建一個 Application 或 Library 工程

工程創(chuàng)建好后庐冯,除了 build.gradle 文件和 src/main 目錄文件夾保留外孽亲,其它的都可以刪除。

第二步:在 src/main 目錄下創(chuàng)建一個 groovy 文件夾

在創(chuàng)建的 Module 工程中的 build.gradle 文件中添加以下依賴:

apply plugin: 'groovy'

dependencies {
    compile gradleApi()
    compile localGroovy()
}

repositories {
    mavenCentral()
}

第三步:在 groovy 文件夾中創(chuàng)建 .groovy 文件

Android Studio 中會自動識別 groovy 文件夾下的 .groovy 文件展父。

第四步:配置運行環(huán)境

  • 點擊 Edit Configurations

1.2 Groovy 基本語法

1.2.1 基本數(shù)據(jù)類型

Groovy 是弱化類型語言返劲,但是實現(xiàn)上還是強類型相關(guān),如果類型不對栖茉,還是會報錯篮绿。Groovy 中的基本數(shù)據(jù)類型有:

  • byte -這是用來表示字節(jié)值。例如2吕漂。
  • short -這是用來表示一個短整型亲配。例如10。
  • int -這是用來表示整數(shù)惶凝。例如1234吼虎。
  • long -這是用來表示一個長整型。例如10000090苍鲜。
  • float -這是用來表示32位浮點數(shù)思灰。例如12.34。
  • double -這是用來表示64位浮點數(shù)坡贺,這些數(shù)字是有時可能需要的更長的十進(jìn)制數(shù)表示官辈。例如12.3456565箱舞。
  • char -這定義了單個字符文字。例如“A”拳亿。
  • Boolean -這表示一個布爾值晴股,可以是true或false。
  • String -這些是以字符串的形式表示的文本肺魁。例如电湘,“Hello World”的。
int a = 4;
float b = 1.0;
double c = 2.1;
byte  te = 4;
char ch = 'c';
String str = "abcd";

1.2.2 變量和方法的聲明

在 Groovy 中通過 def 關(guān)鍵字進(jìn)行變量和方法的聲明鹅经。

def a = 1
def b = 1.0
def str = "Hello World"

def output() {
    print 'Hello World'
}

Groovy 類似腳本寂呛,所以有很多都可以省略:

  • 語句后的分號;可以省略
  • 變量的類型可以省略
  • 方法返回值 return 語句可以省略

方法的聲明

Groovy 也是一門 JVM 語言,所以在語法上與 Java 有很多相通的地方瘾晃,這樣在方法的聲明時候格式也比較隨意贷痪,所以作為 Android 程序員,我們可以選擇靠攏 Java 語法格式的風(fēng)格蹦误。

def methodName() {
    print("HelloWorld")
}

def int sum2Number(a, b) {
    return a + b
}

def sum(a, b) {
    return a+b
}

int add(a ,b) {
    return a + b
}

1.2.3 循環(huán)

Groovy 中循環(huán)控制語句與 Java 中的類似劫拢,有以下三種:

  • for 語句
  • while 語句
  • for-in 語句
for(int i = 0 ;i < 9; i++) {
    println(i)
}

int i = 0
while(i++ < 9) {
    println(i)
}

for(int j in 1..9) {
    println(j)
}

同樣,針對循環(huán)也有循環(huán)控制語句:

  • break:break語句用于結(jié)束循環(huán)和switch語句內(nèi)的控制流强胰。
  • continue:結(jié)束本次循環(huán)舱沧,進(jìn)行下次循環(huán),僅限于while和for循環(huán)偶洋。
for(int i = 0 ;i < 9; i++) {
    println(i)
    continue
}

for(int j in 1..9) {
    println(j)
    break
}

1.2.4 條件判斷語句

Groovy 中的條件判斷語句與 Java 中的類似熟吏,有:

  • if
  • if...else
  • if...else if...else
  • switch

例子就不演示了,語法跟 Java 相同玄窝。

在上面的 Groovy 基礎(chǔ)介紹中牵寺,形式上跟 Java 語言非常相似,沒有太大的變化哆料,針對 Java 缸剪、Android 程序員來說應(yīng)該非常容易上手。

1.3 Groovy 中的集合

Java 中集合主要有:List东亦、Map 衍生出來的類杏节,在 Groovy 中同樣存在集合,名稱跟 Java 中的相同典阵,只不過形式發(fā)生改變了奋渔,更加簡單容易操作。Groovy 中的集合:

  • List:亦稱為列表壮啊,列表是用于存儲數(shù)據(jù)項集合的結(jié)構(gòu)嫉鲸。
  • Map:亦稱為映射,映射(也稱為關(guān)聯(lián)數(shù)組歹啼,字典玄渗,表和散列)是對象引用的無序集合座菠。

1.3.1 List 列表

基本語法:List 列表使用[ ] 進(jìn)行聲明,并通過索引進(jìn)行區(qū)分藤树。

def listEmpty = []    //空列表
def list = [1,2,3,4,5] //整數(shù)值列表
def listInt = [1,[2,3],4,5] //列表嵌套列表
def listString = ["andoter","note"] //字符串列表
def listNone = ["andoter",1,4]  //異構(gòu)對象列表

列表中的方法:

  • boolean add(E e)
  • void add(int index, E element)
  • boolean addAll(Collection<? extends E> c)
  • void clear()
  • boolean contains(Object o)
  • Iterator<E> iterator()
  • Object[] toArray()
  • int lastIndexOf(Object o)
  • E set(int index, E element)

這些方法都跟 Java 中的類似浴滴,打開對應(yīng)的類型查看后,發(fā)現(xiàn)通過 def 聲明的列表竟然是 java.util.List 下面的岁钓。

def listEmpty = []    //空列表
def list = [1,2,3,4,5] //整數(shù)值列表
def listInt = [1,[2,3],4,5] //列表嵌套列表
def listString = ["andoter","note"] //字符串列表
def listNone = ["andoter",1,4]  //異構(gòu)對象列表

listEmpty.add(1)
listEmpty << 6
println(listEmpty.size())
list.clear()
println(listInt.contains([2,3]))
println(listString.lastIndexOf("note"))
println(listNone.indexOf(1))

需要注意升略,在 groovyjarjarantlr.collections.List 包下同樣存在 List,所以使用的時候需要注意屡限。

關(guān)于列表 List 的遍歷品嚣,我們可以參照 Java 中的 Iterator 接口去遍歷,或者使用 Groovy 系統(tǒng)提供的 each 方法進(jìn)行遍歷钧大。在 Groovy 中提供 DefaultGroovyMethods 類翰撑,該類定義很多快捷使用方法:

  • abs:取絕對值計算
  • addAll(Collection)
  • each:遍歷
  • eachWithIndex:帶 index 的遍歷
  • grep:符合條件的element會被提取出來,形成一個list
  • every:所有的element都滿足條件才返回true拓型,否則返回false
  • any:只要存在一個滿足條件的element就返回true额嘿,否則返回false
  • join:用指定的字符連接collection中的element
  • sort:根據(jù)指定條件進(jìn)行排序
  • find:查找collection中滿足條件的‘第一個’element
  • findAll:查找collection中滿足條件的‘所有’element

很多使用的方法,可參照源碼查看劣挫。

def listString = ["andoter","note"]

listString.each {
    println(it)
}

listString.each {
    value -> println(value)
}

1.3.2 Map 映射

Map集合中的元素由鍵值訪問。 Map中使用的鍵可以是任何類东帅。當(dāng)我們插入到Map集合中時压固,需要兩個值:鍵和值。

def mapEmpty = [ : ]
def mapString = ["name":"andoter","email" : "andoter0504@gmail.com"]
def mapInt = ["name" : 123, "age" : 26]

映射中的方法:

  • void clear()
  • boolean containsValue(Object value)
  • Map.Entry<K, V> eldest()
  • Set<Map.Entry<K,V>> entrySet()
  • void forEach(BiConsumer<? super K, ? super V> action)
  • V get(Object key)
  • Set<K> keySet()
  • Collection<V> values()

總體上方法與 Java 中的 Map 相同靠闭。

def mapEmpty = [ : ]
def mapString = ["name":"andoter", "email" : "andoter0504@gmail.com"]
def mapInt = ["name" : 123, "age" : 26]

mapEmpty.put("name","andoter")
mapEmpty.values()
mapString.get("name")
mapInt.containsValue("123")
mapString.each {key, value ->
    if(key == null || key.length() == 0) {
        println("Null Object")
    }

    if(key.equals("name")){
        println(key + "=" + value)
    }else{
        println(key + ":" + value)
    }
}

1.4 Groovy 中的 IO 操作

Java 提供了 java.io.* 一系列方法用于文件的操作帐我,這些方法在 Groovy 中也適用。Groovy 針對 Java 提供的方法做了增強處理愧膀,更方便使用拦键。

def file = new File("/Users/dengshiwei/WorkProject/GradlePlugin/groovydemo/src/main/groovy/TestGroovy.groovy")
if (file.exists()) {
   file.eachLine {
       line ->
           println(line)
   }
} else {
    print("File not exist")
}

這里簡單的示例下,更多的內(nèi)容請參照官方 API 接口檩淋。

1.5 閉包

閉包作為 Groovy 中非常重要的特性芬为,它使得 Groovy 語言更加靈活,在 Gradle 項目構(gòu)建中蟀悦,更是在 DSL 中大量被使用媚朦,所以掌握閉包的使用對掌握 Android 項目構(gòu)建有非常重要的作用。

1.5.1 閉包的定義

閉包的定義格式:

{
    parameters -> statements
}

從形式上來看與 Lambda 表達(dá)式非常類似日戈,所以熟悉 Lambda 表達(dá)式的同學(xué)上手閉包非常簡單询张。如果閉包沒有定義參數(shù),它隱含一個參數(shù) it,類似 Java 中的 this浙炼,假設(shè)你的閉包不需要接受參數(shù),但是還是會生成一個隱式參數(shù) it佩伤,只不過它的值為 null,也就是說拷姿,閉包至少包含一個參數(shù)。

無參數(shù)的閉包

def closure = {
    println("No Parameters")
}

一個參數(shù)的閉包

def closureOneParameters = {
    key -> println(key)
}

兩個參數(shù)的閉包

def closure2Parameter = {
    key,value->
        if (key == 1) {
            key = key + 1
            println(key + ":" + value)
        } else if (key == 2)
            println(key + ":" + value)
}

1.5.2 閉包的特性

閉包的引入讓 Groovy 語言更加簡單恋拷、方便,比如作為函數(shù)的最后一個參數(shù)钮糖,閉包可以單獨寫在函數(shù)梅掠,本小節(jié)中介紹一下閉包常見的使用形式。

閉包特性:

  • 閉包可以訪問外部的變量店归,方法是不能訪問外部變量的阎抒。
  • 閉包中可以包含代碼邏輯,閉包中最后一行語句消痛,表示該閉包的返回值且叁,不論該語句是否冠名return關(guān)鍵字,如果最后一行語句沒有不輸入任何類型秩伞,閉包將返回null逞带。
  • 閉包的參數(shù)聲明寫在‘->’符號前,調(diào)用閉包的的標(biāo)準(zhǔn)寫法是:閉包名.call(閉包參數(shù))纱新。
  • 閉包的一些快捷寫法展氓,當(dāng)閉包作為閉包或方法的最后一個參數(shù)×嘲可以將閉包從參數(shù)圓括號中提取出來接在最后遇汞,如果閉包是唯一的一個參數(shù),則閉包或方法參數(shù)所在的圓括號也可以省略簿废。對于有多個閉包參數(shù)的空入,只要是在參數(shù)聲明最后的,均可以按上述方式省略族檬。

閉包作為函數(shù)參數(shù)

閉包作為函數(shù)參數(shù)時歪赢,跟普通的變量參數(shù)使用方式相同。

def checkKey = {
    map ->
        if (map.size() == 0) {
            println("Parametes is Null or Empty")
        }

        println(map)
}

def enqueue(key, value, closure) {
    def map = [:]
    map.put(key, value)
    closure(map)
}

enqueue(1, 2, checkKey)

通常情況下单料,在函數(shù)具有閉包作為參數(shù)的時候埋凯,會將閉包放在最后一個參數(shù)的位置,當(dāng)閉包作為最后一個參數(shù)的時候看尼,閉包可以抽離到函數(shù)體之外递鹉,提高函數(shù)的簡潔性。

關(guān)于 Groovy 比較好的文章

2. Gradle DSL 語言

上面我們針對 Groovy 語言進(jìn)行簡單的學(xué)習(xí)躏结,接下來就是 Gradle DSL 語言的學(xué)習(xí)。Gradle 是 Android Studio 中采用的全新項目構(gòu)建方式狰域。

Gradle 是一個開源的自動化構(gòu)建工具媳拴,提供更高的靈活和體驗黄橘。Gradle 腳本采用 Groovy 或 Kotlin 進(jìn)行編寫。官方文檔

2.1 基本概念

Gradle 是一種腳本配置屈溉,所以當(dāng)它執(zhí)行的時候塞关,它需要跟對應(yīng)的類型相對應(yīng)。在 Gradle 中存在以下三種類型:

腳本類型 委托的實例
Build script Project
Init script Gradle
Settings script Settings

Gradle 圍繞項目 Project子巾,所以 Project 是我們最重要的接口,通過 Project 接口帆赢,我們可以獲取整個 Gradle 的屬性。通常我們的項目在 Project 模式的下結(jié)構(gòu)是:

├── app #Android App目錄
│   ├── app.iml
│   ├── build #構(gòu)建輸出目錄
│   ├── build.gradle #構(gòu)建腳本
│   ├── libs #so相關(guān)庫
│   ├── proguard-rules.pro #proguard混淆配置
│   └── src #源代碼线梗,資源等
├── build
│   └── intermediates
├── build.gradle #工程構(gòu)建文件
├── gradle
│   └── wrapper
├── gradle.properties #gradle的配置
├── gradlew #gradle wrapper linux shell腳本
├── gradlew.bat
├── LibSqlite.iml
├── local.properties #配置Androod SDK位置文件
└── settings.gradle #工程配置

2.2 Project

2.2.1 生命周期 Lifecycle

Project 與 build.gradle 文件是一對一的關(guān)系椰于,在初始化腳本構(gòu)建的過程中,Gradle 為每一個項目創(chuàng)建 Project對象仪搔。步驟如下:

初始化階段

在初始化階段瘾婿,構(gòu)建工具根據(jù)每個 build.gradle 文件創(chuàng)建出每個項目對應(yīng)的 Project,同時會執(zhí)行項目根目錄下的 settings.gradle 分析需要參與編譯的項目烤咧。

比如我們常見的 settings.gradle 配置文件:

include ':app', ':groovydemo'

指明了需要編譯的項目偏陪。

配置階段

配置階段為每個 Project 創(chuàng)建并配置 Task,配置階段會去加載所有參與構(gòu)建項目的 build.gradle 文件煮嫌,將每個 build.gradle 文件轉(zhuǎn)換為一個 Gradle 的 Project 對象笛谦,分析依賴關(guān)系,下載依賴昌阿。

執(zhí)行階段

Gradle 根據(jù) Task 之間的依賴關(guān)系揪罕,決定哪些 Task 需要執(zhí)行,以及 Task 之間的先后順序宝泵。

2.2.2 Task

Task 是 Gradle 中的最小執(zhí)行單元,所有的構(gòu)建轩娶、編譯儿奶、打包、debug鳄抒、test 等都是執(zhí)行了某一個 task闯捎,一個 Project 可以有多個 Task,Task 之間可以互相依賴许溅。例如我有兩個 Task瓤鼻,TaskA 和 TaskB,指定 TaskA 依賴 TaskB贤重,然后執(zhí)行 TaskA茬祷,這時會先去執(zhí)行 TaskB,TaskB 執(zhí)行完畢后在執(zhí)行 TaskA并蝗。

同時祭犯,我們也可以自定義 Task秸妥,也可以查找 Task 是否存在。站在編程的角度來看 Task 同樣是一個類沃粗,核心方法:

  • String getName():獲取任務(wù)名稱
  • Project getProject():獲取任務(wù)所在的 Project 對象
  • List<Action<? super Task>> getActions():獲取 Action
  • TaskDependency getTaskDependencies():獲取任務(wù)依賴
  • Task dependsOn(Object... var1):任務(wù)依賴關(guān)系函數(shù)
  • void onlyIf(Closure var1)
  • TaskState getState():獲取任務(wù)的狀態(tài)
  • Task doFirst(Closure var1):任務(wù)先執(zhí)行..
  • Task doLast(Closure var1):任務(wù)后執(zhí)行..
  • String getDescription():獲取任務(wù)描述
  • String getGroup():獲取任務(wù)分組
1. 任務(wù)的創(chuàng)建

Task 是 Gradle 中最小執(zhí)行基本單元粥惧,創(chuàng)建任務(wù)的方式有以下幾種:

  • Project.task(String name)
  • Project.task(String name, Closure configureClosure)
  • Project.task(Map<String, ?> args, String name, Closure configureClosure)
  • Project.task(Map<String, ?> args, String name)
  • TaskContainer.create(String name)
  • TaskContainer.create(Map<String,?> options)

前面三種都是基于 Project 提供的 task 重載方法進(jìn)行創(chuàng)建。這里需要著重介紹下里面的 Map 參數(shù)最盅,Map 參數(shù)選項用于控制 Task 的創(chuàng)建以及屬性突雪。

配置項 描述 默認(rèn)值
type 任務(wù)創(chuàng)建的類型 DefaultTask
overwrite 是否重寫已存在的任務(wù) false
dependsOn 添加任務(wù)的依賴 []
action 添加任務(wù)中的 Action null
description 任務(wù)的描述 null
group 配置任務(wù)的分組 null

第一種:直接以任務(wù)名稱創(chuàng)建任務(wù)

Task copyTask = task("copyTask")
copyTask.description = "Copy Task"
copyTask.group = "custom"
copyTask.doLast {
    print("Copy Task Create")
}

這種方式跟 Java 中的創(chuàng)建對象的方式非常相似,這種方式的本質(zhì)是調(diào)用 Project 類中的 task(String name) 方法進(jìn)行對象的創(chuàng)建涡贱。

第二種:task + 閉包的方式

Task taskClosure = project.task("taskClosure"){
    print("Task Closure")
}

這種寫法利用閉包是最后一個參數(shù)的時候咏删,可以抽取到外部寫。上面的這種寫法也可以精簡:

task taskClousre {
    print("Task Closure")
}

這種形式的在 .gradle 腳本文件中用的非常多盼产,所以大家也寫這種吧饵婆!

第三種:task + Map

Task mapTask = project.task(dependsOn: copyTask,description: "mapTask",group: "mapTask",
        "mapTask"){
    println("Map Task Create")
}

這里通過 Map 進(jìn)行 Task 的一些設(shè)置,這里我們可以同樣以方式一一樣戏售,單獨進(jìn)行設(shè)置侨核。

第四種:TaskContainer 創(chuàng)建任務(wù)

project.tasks.create("TaskContainer") {
    description "TaskContainer"
    group "TaskContainer"
    doLast {
        println("TaskContainer")
    }
}

在上面的演示例子中,我們也介紹了任務(wù)的分組和描述的使用灌灾,可以在 Gradle Pojects 欄中進(jìn)行查看任務(wù)的分組和描述搓译。

2. 任務(wù)之間的關(guān)系
  1. dependsOn(Task task) 任務(wù)依賴,通過 dependsOn 可以建立任務(wù)之間的執(zhí)行依賴關(guān)系,先執(zhí)行依賴的任務(wù)锋喜。

    def name = "Hello World from"
    
    task checkName {
        if (name.length() > 0){
            name = name.replace("from", "")
        }
    }
    
    task printName() {
        println(name)
    }
    
    printName.dependsOn(checkName)
    
  2. mustRunAfter(Task task) 必須在添加的任務(wù)之后執(zhí)行些己。

    def name = "Hello World from"
    
    task checkName {
        if (name.length() > 0){
            name = name.concat(" China")
        }
    }
    
    task printName() {
        println(name)
    }
    
    printName.mustRunAfter(checkName)
    
3 任務(wù)類型

在 1 節(jié)中,我們提到了創(chuàng)建任務(wù)時可以通過 Map 配置任務(wù)的依賴屬性關(guān)系嘿般,里面涉及到 任務(wù)類型(type)段标,默認(rèn)值是 DefaultTask 類型,關(guān)于 type炉奴,我的理解是 Groovy 系統(tǒng)的 Task 類型逼庞,我們查看官方文檔,可以看到有很多 Task 類的子類瞻赶,這些應(yīng)該都可以作為 type 值進(jìn)行設(shè)置赛糟?那么常見的任務(wù)類型有哪些呢?

官方 Task API

  1. Copy 類型 Task砸逊,Copy 任務(wù)的方法:
  • eachFile:遍歷文件
  • exclude(Closure excludeSpec):去除包含的內(nèi)容
  • filter(Closure closure):過濾
  • from(Object... sourcePaths):源目錄
  • include(Closure includeSpec):包含內(nèi)容
  • into(Object destDir):目標(biāo)目錄
  • rename(Closure closure):重命名
  • with(CopySpec... sourceSpecs)

通過 Copy 任務(wù)璧南,我們可以更方面實現(xiàn)文件的拷貝復(fù)制類操作,因為它提供了一些封裝好的方法师逸,不需要我們在通過 File 的操作進(jìn)行司倚。常見的就是創(chuàng)建的 makeJar 任務(wù),拷貝系統(tǒng)編譯的 jar 包到指定目錄。

task makeJar(type: Copy) {
    def jarName = 'SensorsAnalytics-Android-SDK-Release-' + version + '.jar';
    delete 'build/libs/' + jarName
    from('build/intermediates/bundles/release/')
    into('build/libs/')
    include('classes.jar')
    rename('classes.jar', jarName)
}

makeJar 的任務(wù)執(zhí)行步驟:首先設(shè)置 type 是 Copy 類型任務(wù),定義拷貝目標(biāo)的 jar 名稱对湃,接著刪除 目標(biāo)目錄已存在的 jar 文件崖叫,from 從源目錄拷貝到 into 的目標(biāo)目錄,包含 include 的 classes.jar拍柒,最后給文件重命名心傀。

在 Android Studio 的 2.X 版本中自動生成的 jar 包路徑在:build/intermediates/bundles/release/ 目錄,在 3.X 中目錄:build/intermediates/packaged-classes/release/拆讯。

  1. Jar 類型 Task
task makeJar(type: Jar, dependsOn: build) {
    delete 'build/libs/sensorsdata.jar'
    from('build/intermediates/classes/release')
    into('build/libs/')
    exclude('android/', 'androidx/','BuildConfig.class', 'R.class')
    exclude {
        it.name.startsWith('R$')
    }
}

Jar 任務(wù)的作用就是打包 jar 包脂男,比如我們通過 from 指定工程目錄中的源碼進(jìn)行打包,這樣我們就可以實現(xiàn)高度的定制化种呐,不像通過 Copy 任務(wù)復(fù)制系統(tǒng)根據(jù)整個目錄生成的 Jar 包一樣宰翅。比如下面的 task 生成 Jar 包。

task makeJars(type: org.gradle.jvm.tasks.Jar) {
    from(android.sourceSets.main.java.srcDirs)
}

在 Gradle 中提供了很多常見的任務(wù):

Gradle System Tasks

2.3. 常見腳本塊

Gradle 支持 Groovy 語言進(jìn)行編寫爽室,非常靈活支持各種插件汁讼。比如你想在腳本中使用一些第三方的插件、類庫等阔墩,就需要自己手動添加對這些插件嘿架、類庫的引用,而這些插件啸箫、類庫又不是直接服務(wù)于項目的耸彪,而是支持其它 build 腳本的運行,所以你應(yīng)當(dāng)將這部分的引用放置在 buildscript 代碼塊中忘苛。 gradle 在執(zhí)行腳本時蝉娜,會優(yōu)先執(zhí)行buildscript代碼塊中的內(nèi)容,然后才會執(zhí)行剩余的build腳本扎唾。所以需要我們了解常見的腳本塊配置召川。

2.3.1 allprojects { }

配置整個 Project 和子項目的配置。

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

比如我們在 allprojects 內(nèi)部定義的 Task 任務(wù)胸遇,就會用于根目錄和子項目扮宠。比如下面的例子,我們執(zhí)行:./gradlew print 任務(wù)狐榔,打印的結(jié)果如下:

allprojects {
    task print {
        println project.name
    }
}

輸出結(jié)果:
GradlePlugin(根目錄)
app
groovydemo
sensorsdatalibrary

2.3.2 buildscript { }

buildscript 中的聲明是 gradle 腳本自身需要使用的資源。

buildscript {
    
    repositories {
        google()
        jcenter()
        mavenCentral()
    }
    //格式為-->group:module:version
    dependencies {
        classpath 'com.android.tools.build:gradle:3.1.2'
        classpath 'com.qihoo360.replugin:replugin-plugin-gradle:2.2.4'
    }
}

2.3.3 configurations { }

配置整個 Project 的 dependency 屬性获雕,與之對應(yīng)的是 ConfigurationContainer薄腻,在 Project 項目中可以通過以下方法獲取 ConfigurationContainer 對象:

  • Project.getConfigurations()
  • configurations

通常使用最多的就是去除依賴,比如我們添加的依賴中也依賴某個庫届案,這種間接依賴的沖突庵楷,transitive dependencies 被稱為依賴的依賴,
稱為“間接依賴”比較合適。

configurations {
    compile.exclude module: 'commons'
    all*.exclude group: 'org.gradle.test.excludes', module: 'reports'
}

2.3.4 dependencies { }

配置項目的依賴庫尽纽,與之對應(yīng)的是 DependencyHandler 類咐蚯。
在 dependencies{} 腳本塊中有不同的依賴方式,這里在 Android Studio 的 2.X 版本與 3.X 版本中差別還是挺大的弄贿,Android Studio3.0 中春锋,compile 依賴關(guān)系已被棄用,被 implementation 和 api 替代差凹,provided 被 compile only 替代期奔,apk 被 runtime only 替代。為了比較方便危尿,前面寫是 3.X 版本呐萌,括號是 2.X。

  • implementation:依賴的庫只能在本項目使用谊娇,外部無法使用肺孤。比如我在一個 libiary 中使用 implementation 依賴了 gson 庫,然后我的主項目依賴了 libiary济欢,那么赠堵,我的主項目就無法訪問 gson 庫中的方法。這樣的好處是編譯速度會加快船逮,推薦使用 implementation 的方式去依賴顾腊,如果你需要提供給外部訪問,那么就使用 api 依賴即可
  • api(compile):使用該方式依賴的庫將會參與編譯和打包
  • testImplementation(testCompile):只在單元測試代碼的編譯以及最終打包測試 Apk 時有效
  • debugImplementation(debugCompile):只在 debug 模式的編譯和最終的 debug Apk 打包時有效
  • releaseImplementation(releaseCompile):僅僅針對 Release 模式的編譯和最終的 Release Apk 打包
  • compileOnly(provided):只在編譯時有效挖胃,不會參與打包杂靶,可以在自己的moudle中使用該方式依賴。比如 com.android.support酱鸭,gson 這些使用者常用的庫吗垮,避免沖突。
  • runtimeOnly(apk):只在生成 Apk 的時候參與打包凹髓,編譯時不會參與烁登,很少用。

下面是一些常見的依賴使用方式:

apply plugin: 'java'
//so that we can use 'compile', 'testCompile' for dependencies

dependencies {
  //for dependencies found in artifact repositories you can use
  //the group:name:version notation
  compile 'commons-lang:commons-lang:2.6'
  testCompile 'org.mockito:mockito:1.9.0-rc1'

  //map-style notation:
  compile group: 'com.google.code.guice', name: 'guice', version: '1.0'

  //declaring arbitrary files as dependencies
  compile files('hibernate.jar', 'libs/spring.jar')

  //putting all jars from 'libs' onto compile classpath
  compile fileTree('libs')
}

在實際項目開發(fā)中蔚舀,我們會引入很多第三方開源庫饵沧,自然就會造成依賴沖突,這里就涉及到在 dependencies 提供的配置字段:

  • force = true:即使在有依賴庫版本沖突的情況下堅持使用被標(biāo)注的這個依賴庫版本
  • transitive = true:依賴的依賴是否可用赌躺,舉個例子狼牺,使用的三方庫中可能也依賴別的庫,我們稱之為“間接依賴”
  • exclude:用于排除指定版本庫礼患,通常用于排除沖突依賴庫
dependencies {
  compile('com.sensorsdata.analytics.android:SensorsAnalyticsSDK:2.0.2') {
    //強制使用我們依賴的 2.0.2 版本庫
    force = true

    //剔除間接依賴的庫,可以通過這三種方式是钥,后面再講解自定義插件的時候就能看懂這三種方式了掠归。
    exclude module: 'cglib' //by artifact name
    exclude group: 'org.jmock' //by group
    exclude group: 'org.unwanted', module: 'iAmBuggy' //by both name and group

    //禁用所有的間接依賴庫
    transitive = false
  }
}

2.3.5 repositories { }

配置 Project 項目所需的倉庫地址,Gradle 必須知道從哪里下載外部依賴悄泥,這是由倉庫配置來指定的虏冻,比如 google()、jcenter() 或 mavenCentral()弹囚。通常在 buildscript 腳本塊中也能看到配置的 repositories 屬性厨相,buildscript 中的聲明是 gradle 腳本自身需要使用的資源,可以聲明的資源包括依賴項余寥、 第三方插件领铐、 maven 倉庫地址等。而在 build.gradle 文件中直接聲明的依賴項宋舷、倉庫地址等信息是項目自身需要的資源绪撵。

repositories {
    //Maven本地倉庫,尋找本地倉庫的邏輯與Maven相同
    mavenLocal()
    //Maven中心倉庫
    mavenCentral()
    //JCenter倉庫
    jcenter()
    //其它Maven遠(yuǎn)程倉庫
    maven {
        //可以指定身份驗證信息
        credentials {
            username 'user'
            password 'password'
        }
        url "http://repo.mycompany.com/maven2"
        //如果上面的URL找不到構(gòu)件祝蝠,則在下面找
        artifactUrls "http://repo.mycompany.com/jars"
    }
    //Ivy遠(yuǎn)程倉庫
    ivy {
        url "http://repo.mycompany.com/repo"
    }
    //Ivy本地倉庫
    ivy {
        url "../local-repo"
    }
    //扁平布局的文件系統(tǒng)倉庫
    flatDir {
        dirs 'lib'
    }
    flatDir {
        dirs 'lib1', 'lib2'
    }
}

2.3.6 sourceSets { }

配置項目的源碼目錄結(jié)構(gòu)音诈。

sourceSets {
    main {
        java {
            srcDirs = ['src/java']
        }
        resources {
            srcDirs = ['src/resources']
        }
    }
}

2.3.7 subprojects { }

用于配置子項目的腳本塊。比如我們在 subprojects 中配置 print 任務(wù)绎狭,則只會作用于子目錄细溅。

subprojects {
    task print {
        println project.name
    }
}

輸出結(jié)果:
app
groovydemo
sensorsdatalibrary

2.3.8 publishing { }

用于發(fā)布構(gòu)建。

publishing {
  publications {
    myPublication(MavenPublication) {
      from components.java
      artifact sourceJar
      pom {
        name = "Demo"
        description = "A demonstration of Maven POM customization"
        url = "http://www.example.com/project"
        licenses {
          license {
            name = "The Apache License, Version 2.0"
            url = "http://www.apache.org/licenses/LICENSE-2.0.txt"
          }
        }
        developers {
          developer {
            id = "johnd"
            name = "John Doe"
            email = "john.doe@example.com"
          }
        }
        scm {
          connection = "scm:svn:http://subversion.example.com/svn/project/trunk/"
          developerConnection = "scm:svn:https://subversion.example.com/svn/project/trunk/"
          url = "http://subversion.example.com/svn/project/trunk/"
        }
      }
    }
  }
}

2.4 Gradle 插件

通過使用插件可以擴(kuò)展項目的功能儡嘶,幫助我們做很多任務(wù)喇聊,比如編譯、打包蹦狂,Gradle 插件可以分為兩類:

  • 二進(jìn)制插件:繼承 org.gradle.api.Plugin 接口實現(xiàn)的插件
  • 腳本插件:直接在 build.gradle 配置文件

2.4.1 二進(jìn)制插件

常見的二進(jìn)制插件 com.android.application誓篱,這里的 ‘com.android.application’ 就是插件的 plugin id,二進(jìn)制插件的使用形式:

apply plugin: plugin id
apply plugin : 'java'

2.4.2 腳本插件

通常腳本插件用于本地的配置存儲凯楔,使用格式:

apply from: ‘fileName’
// config.gradle
rootProject.ext {
    android = [
        compileSdkVersion : 28,
        buildToolsVersion : "28.0.0",
        applicationId : "sw.andoter.com.gradleplugindemo",
        minSdkVersion : 18,
        targetSdkVersion : 28,
        versionCode : 1,
        versionName : "1.0"
    ]

    sdkVersion = 13

    apkPath = [
       apkPath : "/Users/dengshiwei/Desktop/*.apk"
    ]
}

apply from: "../config.gradle"

2.5 Gradle 自定義插件

在基于 Gradle 的項目構(gòu)建中插件的使用非常常見窜骄,比如 com.android.application、com.android.library 等摆屯,如何自定義自己的插件呢邻遏?在 Gradle 中提供了 Plugin 接口用于自定義插件,本小節(jié)只做簡單介紹自定義插件的步驟:

  1. 創(chuàng)建一個 module虐骑,什么樣的都可以准验,不管是 Phone&Tablet Module 或 Android Librarty 都可以,然后只留下 src/main 和 build.gradle廷没,其他的文件全部刪除沟娱。

  2. 在main 目錄下創(chuàng)建 groovy 文件夾,然后在 groovy 目錄下就可以創(chuàng)建我們的包名和 groovy 文件了,記得后綴要以 .groovy 結(jié)尾腕柜。在這個文件中引入創(chuàng)建的包名,然后寫一個 Class 繼承于 Plugin< Project > 并重寫 apply 方法。

    class MyPlugin implements Plugin<Project> {
    
        @Override
        void apply(Project project) {
            System.out.println("-----------插件開始-----------")
            System.out.println("---這是我們的自定義插件---")
            System.out.println("-----------插件結(jié)束-----------")
        }
    }
    
  3. 在 main 目錄下創(chuàng)建 resources文件夾盏缤,繼續(xù)在 resources 下創(chuàng)建 META-INF 文件夾砰蠢,繼續(xù)在 META-INF 文件夾下創(chuàng)建 gradle-plugins 文件夾,最后在 gradle-plugins 文件夾下創(chuàng)建一個 xxx.properties 文件唉铜,注意:這個 xxx 就是在 app 下的 build.gradle 中引入時的名字台舱,例如:apply plugin: ‘xxx’。在文件中寫 implementation-class=implementation-class=com.andoter.customplugin.MyPlugin潭流。

    implementation-class=com.andoter.customplugin.MyPlugin
    
  4. 打開 build.gradle 刪除里面所有的內(nèi)容竞惋。然后格式按這個寫,uploadArchives 是上傳到 maven 庫灰嫉,然后執(zhí)行 uploadArchives 這個 task拆宛,就將我們的這個插件打包上傳到了本地 maven 中,可以去本地的 Maven 庫中查看讼撒。

    apply plugin: 'groovy'
    apply plugin: 'maven'
    
    dependencies {
        compile gradleApi()
        compile localGroovy()
    }
    
    repositories {
        mavenCentral()
    }
    
    group = 'com.andoter.customplugin'
    version = '1.0'
    uploadArchives {
        repositories {
            mavenDeployer {
                repository(url: uri('../repo'))
            }
        }
    }
    

    在上面的實現(xiàn)中浑厚,我們也可以把 group、version 字段配置在內(nèi)部:

    uploadArchives {
        repositories {
            mavenDeployer {
                repository(url: uri('../repo'))
                pom.groupId = "com.andoter.customplugin"
                pom.artifactId = "groovydemo"
                pom.version = "1.0"
            }
        }
    }
    
  5. 應(yīng)用 gradle 插件:在項目下的 build.gradle(也可以在 module 中)中的 repositories 模塊中定義本地 maven 庫地址根盒。在 dependencies 模塊中引入我們的插件的路徑钳幅。

    // 根目錄 .gradle 文件配置插件的地址
    buildscript {
        
        repositories {
            google()
            jcenter()
            mavenCentral()
            maven {
                url './repo'
            }
        }
        //格式為-->group:module:version
        dependencies {
            classpath 'com.android.tools.build:gradle:3.1.2'
            classpath 'com.andoter.customplugin:groovydemo:1.0'
        }
    }
    
    // 子項目使用插件
    apply plugin: 'com.andoter.customplugin'
    

這樣就完成一個自定義插件的使用步驟,自定義插件的核心開發(fā)一個什么樣的插件炎滞,比如結(jié)合 Transform 開發(fā)一個編譯時框架敢艰。

3. Android DSL

Android 提供對應(yīng)的 android 插件用于項目的構(gòu)建配置,在 Android 中項目的類型有以下四種:

  • AppExtension:對應(yīng) com.android.application册赛。
  • LibraryExtension:對應(yīng) com.android.library钠导。
  • TestExtension:對應(yīng) com.android.test。
  • FeatureExtension:對應(yīng) com.android.feature击奶,及時應(yīng)用辈双。

3.1 Android 插件腳本塊配置

Android 構(gòu)建系統(tǒng)編譯應(yīng)用資源和源代碼,然后將它們打包成可供測試柜砾、部署湃望、簽署和分發(fā)的 APK。Android Studio 使用 Gradle 這一高級構(gòu)建工具包來自動化執(zhí)行和管理構(gòu)建流程痰驱,同時也允許您定義靈活的自定義構(gòu)建配置证芭。每個構(gòu)建配置均可自行定義一組代碼和資源,同時對所有應(yīng)用版本共有的部分加以重復(fù)利用担映。Android Plugin for Gradle 與這個構(gòu)建工具包協(xié)作废士,共同提供專用于構(gòu)建和測試 Android 應(yīng)用的流程和可配置設(shè)置。

3.1.1 aaptOptions { }

配置 Android 資源打包工具 AAPT 選項蝇完。

aaptOptions {
    additionalParameters '-S',
            '/Users/yifan/dev/github/Testapp/app/src/main/res3',
            '-S',
            '/Users/yifan/dev/github/Testapp/app/src/main/res2',
            '--auto-add-overlay'
    noCompress 'foo', 'bar'
    ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~'
}

這個選項用于配置 AAPT 資源打包時的一些處理官硝,比如資源替換矗蕊,這塊內(nèi)容可參照編譯時替換資源

3.1.2 adbOptions { }

配置 Android 調(diào)試工具 ADB 選項氢架。通常我們通過 adb 指令來進(jìn)行 Apk 的安裝或卸載傻咖,或者一些文件拷貝工作,通過 adbOptions {} 腳本塊同樣可以在 Android Plugin 中進(jìn)行配置岖研,adbOptions {} 腳本塊對應(yīng) AdbOptions 類卿操,該類有兩個屬性:

  • installOptions:adb 配置選項,是 List<String> 類型孙援。
  • timeOutInMs:是設(shè)置超時時間的害淤,單位是毫秒,這個超時時間是執(zhí)行adb這個命令的超時時間拓售,int 類型窥摄。
android {
    adbOptions {
        timeOutInMs 10 * 1000
        installOptions '-r','-s'
    }
}
  • -l:鎖定該應(yīng)用程序
  • -r:替換已存在的應(yīng)用程序,也就是我們說的強制安裝
  • -t:允許測試包
  • -s:把應(yīng)用程序安裝到 SD 卡上
  • -d:允許進(jìn)行降級安裝邻辉,也就是安裝的比手機上帶的版本低
  • -g:為該應(yīng)用授予所有運行時的權(quán)限

3.1.3 buildTypes { }

當(dāng)前項目的構(gòu)建類型配置溪王,對應(yīng)的配置類型 BuildType 類,在 Android Studio 的中已經(jīng)給我們內(nèi)置了 release 和 debug 兩種構(gòu)建類型值骇,這兩種模式的主要差異在于能否在設(shè)備上調(diào)試以及簽名不一樣莹菱,這里會涉及到很多屬性可以配置。

  • applicationIdSuffix:配置基于應(yīng)用默認(rèn)的 applicationId 的后綴吱瘩,常用于構(gòu)建變體應(yīng)用道伟。
  • consumerProguardFiles:配置 .aar 文件中是否使用 Proguard 混淆。
  • crunchPngs:針對 png 的優(yōu)化使碾,設(shè)置為 true 的時候會增加編譯時間蜜徽。
  • debuggable:配置構(gòu)建的 apk 是否能夠進(jìn)行 debug。
  • javaCompileOptions:配置 Java 編譯的配置
  • jniDebuggable:配置構(gòu)建類型的 apk native code 是否能夠進(jìn)行 debug票摇。
  • minifyEnabled:是否啟用 Proguard 混淆拘鞋。
  • multiDexEnabled:是否使用分包。
  • multiDexKeepFile:指定放到主 dex 中的文件矢门。
  • multiDexKeepProguard:配置指定的文件使用 Proguard盆色。
  • proguardFiles:混淆文件。
  • shrinkResources:用于配置是否自動清理未使用的資源祟剔,默認(rèn)為 false隔躲。
  • signingConfig:簽名文件。
  • useProguard
  • versionNameSuffix:類似于 applicationIdSuffix物延。
  • zipAlignEnabled:zipAlign 優(yōu)化 apk 文件的工具宣旱。
buildTypes {

    release {
        minifyEnabled false
        crunchPngs true
        debuggable false
        shrinkResources true
        multiDexEnabled true
        multiDexKeepProguard file('proguard-rules.pro') // keep specific classes using proguard syntax
        multiDexKeepFile file('multiDexKeep.txt')
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        signingConfig
        zipAlignEnabled true
        applicationIdSuffix '.release'
        versionNameSuffix '.release'
    }

    debug {
        applicationIdSuffix '.debug'
        versionNameSuffix '.debug'
    }
}

3.1.4 compileOptions { }

Java 編譯選項,通常是針對 JDK 進(jìn)行編碼格式修改或者指定 JDK 的版本,對應(yīng)的類是 CompileOptions叛薯。

compileOptions {
    encoding = 'utf-8'
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}

3.1.5 dataBinding { }

DataBinding 配置選項浑吟,查看源碼可以發(fā)現(xiàn)對應(yīng) DataBindingOptions 類笙纤,該類包含四個屬性:

  • version:版本號。
  • enabled:是否可用组力。
  • addDefaultAdapters:是否使用默認(rèn)的 Adapter粪糙。
  • enabledForTests:是否用于 Test。
dataBinding {
    enabled true
    version 1.0
}

3.1.6 defaultConfig { }

defaultConfig 也是 Android 插件中常見的一個配置塊忿项,負(fù)責(zé)默認(rèn)的所有配置。同樣它是一個 ProductFlavor城舞,如果一個 ProductFlavor 沒有被特殊配置,則默認(rèn)使用 defaultFlavor 的配置家夺,比如報名、版本號榨为、版本名稱等随闺。常見的屬性有:

  • applicationId:applicationId 是 ProductFlavor 的一個屬性蔓腐,用于配置 App 生成的進(jìn)程名,默認(rèn)情況下是 null回论。
  • minSdkVersion:指定 Apk 支持的最低 Android 操作系統(tǒng)版本散罕。
  • targetSdkVersion:用于配置 Apk 基于的 SDK 哪個版本進(jìn)行開發(fā)。
  • versionCode:同樣是 ProductFlavor 的一個屬性傀蓉,配置 Apk 的內(nèi)部版本號欧漱。
  • versionName:配置版本名稱。
  • testApplicationId:配置測試 App 的報名葬燎,默認(rèn)情況下是 applicationId + ".test"误甚。
  • testInstrumentationRunner:配置單元測試使用的 Runner,默認(rèn)是 android.test.InstrumentationTestRunner,或者可以使用自定義的 Runner萨蚕。
  • signingConfig:配置默認(rèn)的簽名信息靶草,對生成的 App 簽名。
  • proguardFile:用于配置使用的混淆文件岳遥。
  • proguardFiles:配置混淆使用的文件奕翔,可以配置多個。
defaultConfig {
    applicationId 'com.andoter.dsw'
    minSdkVersion 15
    targetSdkVersion 28
    versionCode 1
    versionName "1.0"
    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    signingConfigs signingConfigs.release
}

3.1.7 dexOptions { }

dex 的配置項浩蓉,通常在開發(fā)的過程中派继,我們可以通過配置 dexOptions {} 提高編譯速度,與之對應(yīng)的是 DexOptions 接口庆猫,該接口由 DefaultDexOptions 默認(rèn)實現(xiàn),DefaultDexOptions 類中包含以下屬性:

  • preDexLibraries:默認(rèn) true
  • jumboMode:默認(rèn) false
  • dexInProcess:默認(rèn)為 true杉畜,所有的 dex 都在 process 中,提高效率
  • javaMaxHeapSize:最大的堆大小
  • maxProcessCount:最大的 process 個數(shù)
  • threadCount:線程個數(shù)
dexOptions {
    incremental true //是否增量灭袁,如果開啟multi-dex, 此句無效
    preDexLibraries true
    javaMaxHeapSize "4g" //java 編譯的 Heap 大小
    jumboMode true
    threadCount 8 //gradle輸就輸在了并行上, 都是串行, 增加線程數(shù)沒用
    // 設(shè)置最大的進(jìn)程數(shù):Memory = maxProcessCount * javaMaxHeapSize
    maxProcessCount 8
}

3.1.8 lintOptions { }

Lint 是Android Studio 提供的 代碼掃描分析工具,它可以幫助我們發(fā)現(xiàn)代碼結(jié)構(gòu)/質(zhì)量問題举娩,同時提供一些解決方案铜涉,而且這個過程不需要我們手寫測試用例。Lint 發(fā)現(xiàn)的每個問題都有描述信息和等級(和測試發(fā)現(xiàn) bug 很相似)盖彭,我們可以很方便地定位問題同時按照嚴(yán)重程度
進(jìn)行解決铺呵。

android {
    lintOptions {
        // true--關(guān)閉lint報告的分析進(jìn)度
        quiet true
        // true--錯誤發(fā)生后停止gradle構(gòu)建
        abortOnError false
        // true--只報告error
        ignoreWarnings true
        // true--忽略有錯誤的文件的全/絕對路徑(默認(rèn)是true)
        //absolutePaths true
        // true--檢查所有問題點,包含其他默認(rèn)關(guān)閉項
        checkAllWarnings true
        // true--所有warning當(dāng)做error
        warningsAsErrors true
        // 關(guān)閉指定問題檢查
        disable 'TypographyFractions','TypographyQuotes'
        // 打開指定問題檢查
        enable 'RtlHardcoded','RtlCompat', 'RtlEnabled'
        // 僅檢查指定問題
        check 'NewApi', 'InlinedApi'
        // true--error輸出文件不包含源碼行號
        noLines true
        // true--顯示錯誤的所有發(fā)生位置沪饺,不截取
        showAll true
        // 回退lint設(shè)置(默認(rèn)規(guī)則)
        lintConfig file("default-lint.xml")
        // true--生成txt格式報告(默認(rèn)false)
        textReport true
        // 重定向輸出;可以是文件或'stdout'
        textOutput 'stdout'
        // true--生成XML格式報告
        xmlReport false
        // 指定xml報告文檔(默認(rèn)lint-results.xml)
        xmlOutput file("lint-report.xml")
        // true--生成HTML報告(帶問題解釋讥脐,源碼位置魏滚,等)
        htmlReport true
        // html報告可選路徑(構(gòu)建器默認(rèn)是lint-results.html )
        htmlOutput file("lint-report.html")
        //  true--所有正式版構(gòu)建執(zhí)行規(guī)則生成崩潰的lint檢查更哄,如果有崩潰問題將停止構(gòu)建
        checkReleaseBuilds true
        // 在發(fā)布版本編譯時檢查(即使不包含lint目標(biāo))觅捆,指定問題的規(guī)則生成崩潰
        fatal 'NewApi', 'InlineApi'
        // 指定問題的規(guī)則生成錯誤
        error 'Wakelock', 'TextViewEdits'
        // 指定問題的規(guī)則生成警告
        warning 'ResourceAsColor'
        // 忽略指定問題的規(guī)則(同關(guān)閉檢查)
        ignore 'TypographyQuotes'
    }
}

3.1.9 packagingOptions { }

Android 打包配置項,可以配置打包的時候哪些打包進(jìn) Apk赢赊。當(dāng)項目中依賴的第三方庫越來越多時,有可能會出現(xiàn)兩個依賴庫中存在同一個
(名稱)文件玩讳。如果這樣熏纯,Gradle在打包時就會提示錯誤(警告)。那么就可以根據(jù)提示往扔,然后使用以下方法將重復(fù)的文件剔除吭服。

packagingOptions {
    //這個是在同時使用butterknife艇棕、dagger2做的一個處理沼琉。同理脓鹃,遇到類似的問題,只要根據(jù)gradle的提示妆档,做類似處理即可。
    exclude 'META-INF/services/javax.annotation.processing.Processor'
}

3.1.10 productFlavors { }

用于構(gòu)建不同的產(chǎn)品風(fēng)味碰镜,在上面我們提到 defaultConfig{} 也是一種產(chǎn)品風(fēng)味,可作為所有產(chǎn)品風(fēng)味的“基類”共同部分杰刽。風(fēng)味(Flavor) 對應(yīng) ProductFlavor 類滓鸠,該類的屬性與配置屬性相匹配。

android {
    ...
    defaultConfig {...}
    buildTypes {...}
    productFlavors {
        demo {
            applicationIdSuffix ".demo"
            versionNameSuffix "-demo"
        }
        full {
            applicationIdSuffix ".full"
            versionNameSuffix "-full"
        }
    }
}

在創(chuàng)建和配置您的產(chǎn)品風(fēng)味之后珠月,在通知欄中點擊 Sync Now啤挎。同步完成后,Gradle會根據(jù)您的構(gòu)建類型和產(chǎn)品風(fēng)味自動創(chuàng)建構(gòu)建變體伙判,
并按照 <product-flavor><Build-Type>的格式命名這些變體宴抚。例如危虱,如果您創(chuàng)建了“演示”和“完整”這兩種產(chǎn)品風(fēng)味并保留默認(rèn)的“調(diào)試”和“發(fā)布”構(gòu)建類型,Gradle 將創(chuàng)建以下構(gòu)建變體:

  • 演示調(diào)試
  • 演示發(fā)布
  • 完整調(diào)試
  • 完整發(fā)布

您可以將構(gòu)建變體更改為您要構(gòu)建并運行的任何變體,只需轉(zhuǎn)到 Build > Select Build Variant延届,然后從下拉菜單中選擇一個變體厕吉。

產(chǎn)品風(fēng)味組合

通常在適配多個渠道的時候龄减,需要為特定的渠道做部分特殊的處理署隘,這里就會涉及到結(jié)合 buildTypes{} 組合不同的產(chǎn)品風(fēng)味組合。

android {
  ...
  buildTypes {
    debug {...}
    release {...}
  }

  // Specifies the flavor dimensions you want to use. The order in which you
  // list each dimension determines its priority, from highest to lowest,
  // when Gradle merges variant sources and configurations. You must assign
  // each product flavor you configure to one of the flavor dimensions.
  flavorDimensions "api", "mode"

  productFlavors {
    demo {
      // Assigns this product flavor to the "mode" flavor dimension.
      dimension "mode"
      ...
    }

    full {
      dimension "mode"
      ...
    }

    // Configurations in the "api" product flavors override those in "mode"
    // flavors and the defaultConfig {} block. Gradle determines the priority
    // between flavor dimensions based on the order in which they appear next
    // to the flavorDimensions property above--the first dimension has a higher
    // priority than the second, and so on.
    minApi24 {
      dimension "api"
      minSdkVersion '24'
      // To ensure the target device receives the version of the app with
      // the highest compatible API level, assign version codes in increasing
      // value with API level. To learn more about assigning version codes to
      // support app updates and uploading to Google Play, read Multiple APK Support
      versionCode 30000 + android.defaultConfig.versionCode
      versionNameSuffix "-minApi24"
      ...
    }

    minApi23 {
      dimension "api"
      minSdkVersion '23'
      versionCode 20000  + android.defaultConfig.versionCode
      versionNameSuffix "-minApi23"
      ...
    }

    minApi21 {
      dimension "api"
      minSdkVersion '21'
      versionCode 10000  + android.defaultConfig.versionCode
      versionNameSuffix "-minApi21"
      ...
    }
  }
}

Gradle 創(chuàng)建的構(gòu)建變體數(shù)量等于每個風(fēng)味維度中的風(fēng)味數(shù)量與您配置的構(gòu)建類型數(shù)量的乘積畅哑。在 Gradle 為每個構(gòu)建變體或?qū)?yīng) APK 命名時砂客,屬于較高優(yōu)先級風(fēng)味維度的產(chǎn)品風(fēng)味首先顯示,之后是較低優(yōu)先級維度的產(chǎn)品風(fēng)味彤恶,再之后是構(gòu)建類型钞钙。以上面的構(gòu)建配置為例,Gradle 可以使用以下命名方案創(chuàng)建總共 12 個構(gòu)建變體:

構(gòu)建變體:[minApi24, minApi23, minApi21][Demo, Full][Debug, Release]
對應(yīng) APK:app-[minApi24, minApi23, minApi21]-[demo, full]-[debug, release].apk

同樣我們在 Terminal 中通過 gradle 指令執(zhí)行構(gòu)建變體的任務(wù):變體名稱(flavorDimensions 的第一個維度的 Flavor) + flavorDimensions 的別的維度的 Flavor + buildTypes声离,例如:minApi24DemoDebug芒炼,構(gòu)建出來的對應(yīng) Apk:app-minApi24-demo-debug.apk

過濾變體

Gradle 會自動根據(jù)設(shè)置的 buildTypes{} 和 flavorDimensions{} 創(chuàng)建很多變體的組合體,但是有些是我們不需要的术徊,這里就涉及到過濾變體的操作本刽。Gradle 提供了 variantFilter {} 腳本塊來過濾笋除,腳本塊對應(yīng) VariantFilter 接口墙歪,接口中包含:

  • setIgnore(boolean ignore):設(shè)置是否忽略某個變體
  • getBuildType:獲取變體的構(gòu)建類型
  • List<ProductFlavor> getFlavors():返回所有變體的列表
  • getName:獲取變體的 name 名稱
variantFilter { variant ->
    def names = variant.flavors.name
    // To check for a certain build type, use variant.buildType.name == "<buildType>"
    if (names.contains("minApi23") && names.contains("demo")) {
        // Gradle ignores any variants that satisfy the conditions above.
        setIgnore(true)
    } else {
        setIgnore(false)
    }
}

更多關(guān)于構(gòu)建變體內(nèi)容參照官方:構(gòu)建變體

3.1.11 signingConfigs { }

配置簽名信息址愿,常用于 BuildType 和 ProductFlavor 配置跋炕,在構(gòu)建變體的過程中,會出現(xiàn)很多種類德召,所以針對不同類別的變體所使用的簽名可能也是不同的敬锐,這就需要使用 signingConfigs{} 配置簽名信息合集赞赖,然后按需所取。


signingConfigs {
    release {//發(fā)布版本的簽名配置
      storeFile file(props['KEYSTORE_FILE'])
      keyAlias props['KEY_ALIAS']
      storePassword props['KEYSTORE_PWD']
      keyPassword props['KEY_PWD']
    }
    debug {//調(diào)試版本的簽名配置
      storeFile file(props['DEBUG_KEYSTORE'])
      keyAlias props['DEBUG_ALIAS']
      storePassword props['DEBUG_KEYSTORE_PWD']
      keyPassword props['DEBUG_KEY_PWD']
    }
}

buildTypes {
    signingConfig signingConfigs.release
}

3.1.12 sourceSets { }

在 AndroidStudio 中,在 src/main/java 目錄下創(chuàng)建我們的 .java 文件顾复,這些都是系統(tǒng)通過 sourceSet{} 設(shè)置好的,比如我們在外部創(chuàng)建一個文件夾,選中文件夾右鍵就無創(chuàng)建 .java 文件的選項站欺。就需要我們通過 sourceSets 進(jìn)行配置,腳本塊對應(yīng) AndroidSourceSet 接口蟆技,接口中有:

  • AndroidSourceSet java(Closure configureClosure):配置 java 文件存放路徑
  • AndroidSourceSet resources(Closure configureClosure):配置 resource 目錄
  • AndroidSourceSet jniLibs(Closure configureClosure):配置 jniLibs 目錄
  • AndroidSourceSet jni(Closure configureClosure):配置 jni 文件目錄
  • AndroidSourceSet renderscript(Closure configureClosure):配置 renderscript 目錄
  • AndroidSourceSet aidl(Closure configureClosure):配置 aidl 文件目錄
  • AndroidSourceSet assets(Closure configureClosure):配置 assets 目錄
  • AndroidSourceSet res(Closure configureClosure):配置 res 目錄
  • AndroidSourceSet manifest(Closure configureClosure):配置 manifest 目錄
sourceSets {
    main {
        java {
            srcDir 'src/main/testjava'
        }
        resources {
            srcDir 'src/resources'
        }
    }
}

3.1.13 splits { }

拆分機制比起使用 flavors界酒,能讓應(yīng)用程序更有效地構(gòu)建一些形式的多個apk寞肖。
多 apk 只支持以下類型:

  • 屏幕密度
  • ABI

腳本塊對應(yīng) Splits 類琼稻,該類中有三個屬性:

  • density:像素密度
  • abi:abi 類型
  • language:語言
splits {
    density {
        enable true
        exclude "ldpi", "tvdpi", "xxxhdpi"
        compatibleScreens 'small', 'normal', 'large', 'xlarge'
    }

    abi {
        enable true
        reset()
        include 'x86', 'armeabi-v7a', 'mips'
        universalApk true
    }
}

4. 發(fā)現(xiàn)好資料

Gradle 的功能非常強大横殴,這里也僅僅針對 Android 項目構(gòu)建常用的進(jìn)行總結(jié)瞄崇,Gradle 還可用于單元測試制妄,或者用于別的項目構(gòu)建,上傳構(gòu)建很多部分诗芜。具體可以參照官方 API 文檔瞳抓。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市伏恐,隨后出現(xiàn)的幾起案子孩哑,更是在濱河造成了極大的恐慌,老刑警劉巖翠桦,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件横蜒,死亡現(xiàn)場離奇詭異,居然都是意外死亡销凑,警方通過查閱死者的電腦和手機丛晌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來斗幼,“玉大人澎蛛,你說我怎么就攤上這事⊥闪” “怎么了谋逻?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長桐经。 經(jīng)常有香客問我毁兆,道長,這世上最難降的妖魔是什么阴挣? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任气堕,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘送巡。我一直安慰自己,他們只是感情好盒卸,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布骗爆。 她就那樣靜靜地躺著,像睡著了一般蔽介。 火紅的嫁衣襯著肌膚如雪摘投。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天虹蓄,我揣著相機與錄音犀呼,去河邊找鬼。 笑死薇组,一個胖子當(dāng)著我的面吹牛外臂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播律胀,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼宋光,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了炭菌?” 一聲冷哼從身側(cè)響起罪佳,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎黑低,沒想到半個月后赘艳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡克握,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年蕾管,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片菩暗。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡娇掏,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出勋眯,到底是詐尸還是另有隱情婴梧,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布客蹋,位于F島的核電站塞蹭,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏讶坯。R本人自食惡果不足惜番电,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧漱办,春花似錦这刷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至洞辣,卻和暖如春咐刨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背扬霜。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工定鸟, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人著瓶。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓联予,卻偏偏與公主長得像,于是被迫代替她去往敵國和親材原。 傳聞我的和親對象是個殘疾皇子躯泰,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容