學(xué)習(xí)了 Gradle 的基礎(chǔ)知識(shí)與原理,咱們?cè)賮?lái)看 build.gradle 的配置詳解,見(jiàn)下方內(nèi)容:
一、Project 的 build.gradle 文件
// Top-level build file where you can add configuration options common to all sub-projects/modules.
// 翻譯:頂級(jí)生成文件,您可以在其中添加所有子項(xiàng)目/模塊通用的配置選項(xiàng)。
buildscript {//這里是gradle腳本執(zhí)行所需依賴,分別是對(duì)應(yīng)的maven庫(kù)和插件
ext.kotlin_version='1.5.30'
repositories {
google()//從Android Studio3.0后新增了google()配置,可以引用google上的開(kāi)源項(xiàng)目
jcenter()//是一個(gè)類似于github的代碼托管倉(cāng)庫(kù),聲明了jcenter()配置,可以輕松引用 jcenter上的開(kāi)源項(xiàng)目
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.0'////此處是android的插件gradle,gradle是一個(gè)強(qiáng)大的項(xiàng)目構(gòu)建工具
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {//這里是項(xiàng)目本身需要的依賴,比如項(xiàng)目所需的maven庫(kù)
repositories {
google()
jcenter()
}
}
// 運(yùn)行g(shù)radle clean時(shí),執(zhí)行此處定義的task任務(wù)。
// 該任務(wù)繼承自Delete,刪除根目錄中的build目錄。
// 相當(dāng)于執(zhí)行Delete.delete(rootProject.buildDir)。
// gradle使用groovy語(yǔ)言,調(diào)用method時(shí)可以不用加()。
task clean(type: Delete) {
delete rootProject.buildDir
}
1. buildscript{}
閉包里是 gradle 腳本執(zhí)行所需依賴,分別是對(duì)應(yīng)的 maven 庫(kù)和第三方插件。
1.1 repositories{} 閉包:
配置遠(yuǎn)程倉(cāng)庫(kù),該閉包中聲明了 jcenter() 和 google() 的配置,其中 jcenter 是一個(gè)代碼托管倉(cāng)庫(kù),上面托管了很多 Android 開(kāi)源項(xiàng)目,在這里配置了 jcenter 后我們可以在項(xiàng)目中方便引用 jcenter 上的開(kāi)源項(xiàng)目,從 Android Studio3.0 后新增了 google() 配置,可以引用 google 上的開(kāi)源項(xiàng)目。
1.2 dependencies{} 閉包:
配置構(gòu)建工具,該閉包使用 classpath 聲明了一個(gè) Gradle 插件,由于 Gradle 并不只是用來(lái)構(gòu)建 Android 項(xiàng)目,因此此處引入相關(guān)插件來(lái)構(gòu)建 Android 項(xiàng)目,其中 '3.0.0' 為該插件的版本號(hào),可以根據(jù)最新的版本號(hào)來(lái)調(diào)整。
2. allprojects{}
閉包里是項(xiàng)目本身需要的依賴,比如項(xiàng)目所需的 maven 庫(kù)。
問(wèn):
為什么同一個(gè) build.gradle(Project)文件中 buildscript 和 allprojects 里面的內(nèi)容基本上是一樣的呢,
他們的區(qū)別在哪?
答:
buildscript 中的聲明是 gradle 腳本自身需要使用的資源,
就是說(shuō)他是 gradle 自己需要的資源,跟 module 其實(shí)并沒(méi)有什么關(guān)系。
而 allprojects 聲明的卻是你所有 module 所需要使用的資源,
就是說(shuō)如果你的每個(gè) module 都需要用同一個(gè)第三庫(kù)的時(shí)候,你可以在 allprojects 里面聲明。
3. task clean(type: Delete){}**
運(yùn)行 gradle clean 時(shí),執(zhí)行此處定義的 task。該任務(wù)繼承自 Delete,刪除根目錄中的 build 目錄。相當(dāng)于執(zhí)行 Delete.delete(rootProject.buildDir)。其實(shí)這個(gè)任務(wù)的執(zhí)行就是可以刪除生成的 Build 文件的,跟 Android Studio 的 clean 是一個(gè)道理。
4. ext
ext 是自定義屬性,現(xiàn)在很多人都喜歡把所有關(guān)于版本的信息都利用 ext 直接在此文件中用(咱們的項(xiàng)目就這樣用的),或者放在另一個(gè)自己新建的 gradle 文件(version.gradle)中集中管理,這樣在 build.gradle 文件中輸入了 apply from:'version.gradle' 這句話,我們就可以讀取到該文件下 ext 的信息,version.gradle 內(nèi)容如下:

講完 Project 的 build 文件,就來(lái)講講最后也是內(nèi)容最多的文件了。
二、Module 的 build.gradle 文件:
從文件內(nèi)容可以看出,主要分為三大部分,如下圖所示:
//--------------------------------------------------第一部分----------------------------------------
plugins {
id 'com.android.application'
}
//或 apply plugin: 'com.android.application'
//--------------------------------------------------第二部分----------------------------------------
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.example.lifecycledemo"
minSdkVersion 19
targetSdkVersion 30
versionCode 1
versionName "1.0"
multiDexEnabled true
ndk {
abiFilters('armeabi-v7a', 'arm64-v8a')
}
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
//目錄指向配置
sourceSets {
main {
//指定lib庫(kù)目錄
jniLibs.srcDirs = ['libs']
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
// 配置 Java 編譯(編碼格式、編譯級(jí)別、生成字節(jié)碼版本)
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
//--------------------------------------------------第三部分----------------------------------------
dependencies {
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
1. plugins{}
在講 AGP 的時(shí)候有講到一丟丟,就是通過(guò) plugins {...} 引入插件。
這種叫做引入 Gradle 插件,而 Gradle 插件大致分為分為兩種:
-
apply plugin:'×××':叫做二進(jìn)制插件,二進(jìn)制插件一般都是被打包在一個(gè)jar里獨(dú)立發(fā)布的,比如我們自定義的插件,再發(fā)布的時(shí)候我們也可以為其指定plugin id,這個(gè)plugin id最好是一個(gè)全限定名稱,就像你的包名一樣; -
apply from:'×××':叫做應(yīng)用腳本插件,其實(shí)這不能算一個(gè)插件,它只是一個(gè)腳本。應(yīng)用腳本插件,其實(shí)就是把這個(gè)腳本加載進(jìn)來(lái),和二進(jìn)制插件不同的是它使用的是from關(guān)鍵字.后面緊跟的坫一個(gè)腳本文件,可以是本地的,也可以是網(wǎng)絡(luò)存在的,如果是網(wǎng)絡(luò)上的話要使用HTTP URL。
雖然腳本插件不是一個(gè)真正的插件,但是不能忽視它的作用.它是腳本文件模塊化的基礎(chǔ),我們可以把龐大的腳本文件進(jìn)行分塊、分段整理,拆分成一個(gè)個(gè)共用、職責(zé)分明的文件,然后使用 apply from 來(lái)引用它們,比如我們可以把常用的函數(shù)放在一個(gè) Utils.gradle 腳本里,供其他腳本文件引用。示例中我們把 App 的版本名稱和版本號(hào)單獨(dú)放在一個(gè)腳本文件里,清晰、簡(jiǎn)單、方便、快捷.我們也可以使用自動(dòng)化對(duì)該文件自動(dòng)處理,生成版本。
看下面代碼:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-parcelize'
apply plugin: 'walle'
apply from: 'walle.gradle'
apply plugin: 'com.huawei.agconnect'
這里就引用了腳本插件:walle.gradle,可去項(xiàng)目中看源碼。
說(shuō)說(shuō) Gradle 插件的作用
把插件應(yīng)用到你的項(xiàng)目中,插件會(huì)擴(kuò)展項(xiàng)目的功能,幫助你在項(xiàng)目的構(gòu)建過(guò)程中做很多事情。
- 可以添加任務(wù)到你的項(xiàng)目中,幫你完成一些亊情,比如測(cè)試、編譯、打包。
- 可以添加依賴配置到你的項(xiàng)目中,我們可以通過(guò)它們配置我們項(xiàng)目在構(gòu)建過(guò)程中需要的依賴.比 如我們編譯的時(shí)候依賴的第三方庫(kù)等。
- 可以向項(xiàng)目中現(xiàn)有的對(duì)象類型添加新的擴(kuò)展屬性、 方法等,讓你可以使用它們幫助我們配置、優(yōu)化構(gòu)建,比如
android{}這個(gè)配置塊就是Android Gradle插件為Project對(duì)象添加的一個(gè)擴(kuò)展。 - 可以對(duì)項(xiàng)目進(jìn)行一些約定,比如應(yīng)用
Java插件之后,約定src/main/java目錄下是我們的源代碼存放位置,在編譯的時(shí)候也是編譯這個(gè)目錄下的Java源代碼文件。
然后我們說(shuō)說(shuō) 'com.android.application'
Android Gradle 插件的分類其實(shí)是根據(jù) Android 工程的屬性分類的。在 Andriod 中有 3 類工程,一類是 App 應(yīng)用工程,它可以生成一個(gè)可運(yùn)行的apk應(yīng)用;一類是 Library 庫(kù)工程,它可以生成 AAR 包給其他的 App 工程公用,就和我們的 Jar 一樣,但是它包含了 Android 的資源等信息,是一個(gè)特殊的 Jar 包;最后一類是 Test 測(cè)試工程,用于對(duì) App 工程或者 Library 庫(kù)工程進(jìn)行單元測(cè)試。
-
App插件id:com.android.application -
Library插件id:com.android.library -
Test插件id:com.android.test
一般一個(gè)項(xiàng)目只會(huì)設(shè)置一個(gè) App 插件,而 module 一般是會(huì)設(shè)置為 Library 插件
2. android{}
是 Android 插件提供的一個(gè)擴(kuò)展類型,可以讓我們自定義 Android Gradle 工程,是 Android Gradle 工程配置的唯一入口。
2.1 compileSdkVersion
是編譯所依賴的 Android SDK 的版本,這里是 API Level 。
2.2 buildToolsVersion
是構(gòu)建該 Android 工程所用構(gòu)建工具的版本。
2.3 defaultConfig{}
-
applicationId
配置我們的包名,包名是app的唯一標(biāo)識(shí),其實(shí)他跟AndroidManifest里面的package是可以不同的,他們之間并沒(méi)有直接的關(guān)系。package指的是代碼目錄下路徑;applicationId指的是app對(duì)外發(fā)布的唯一標(biāo)識(shí),會(huì)在簽名、申請(qǐng)第三方庫(kù)、發(fā)布時(shí)候用到。 -
minSdkVersion
是支持的Android系統(tǒng)的api level,這里是19,也就是說(shuō)低于Android 19版本的機(jī)型不能使用這個(gè)app。 -
targetSdkVersion
表明我們是基于哪個(gè)Android版本開(kāi)發(fā)的,這里是30。 -
versionCode
表明我們的app應(yīng)用內(nèi)部版本號(hào),一般用于控制app升級(jí),當(dāng)然我在使用的bugly自動(dòng)升級(jí)能不能接受到升級(jí)推送就是基于這個(gè)。 -
versionName
表明我們的app應(yīng)用的版本名稱,一般是發(fā)布的時(shí)候?qū)懺?app上告訴用戶的,這樣當(dāng)你修復(fù)了一個(gè)bug并更新了版本,別人卻發(fā)現(xiàn)說(shuō)怎么你這個(gè)bug還在,你這時(shí)候就可以自信的告訴他自己看下app的版本號(hào)。(親身經(jīng)歷在撕逼的時(shí)候可以從容的應(yīng)對(duì)) -
multiDexEnabled
用于配置該BuildType是否啟用自動(dòng)拆分多個(gè)Dex的功能。一般用程序中代碼太多,超過(guò)了65535個(gè)方法的時(shí)候。 -
ndk{}
多平臺(tái)編譯,生成有so包的時(shí)候使用,包括四個(gè)平臺(tái)'armeabi', 'x86', 'armeabi-v7a', 'mips'。一般使用第三方提供的SDK的時(shí)候,可能會(huì)附帶so庫(kù)。 -
sourceSets
源代碼集合,是Java插件用來(lái)描述和管理源代碼及資源的一個(gè)抽象概念,是一個(gè)Java源代碼文件和資源文件的集合,我們可以通過(guò)sourceSets更改源集的Java目錄或者資源目錄等。
譬如像上面代碼,配置 jniLibs.srcDirs = ['libs'],可以在 Android studio 的 Android 視圖下生成 jniLibs 文件夾,可以方便我們存放 jar 包和庫(kù)文件,其中 Android 視圖下的 jniLibs 和 project 視圖下的 libs 指向同一文件夾(app → libs),如下圖所示:

-
flavorDimensions
官網(wǎng)翻譯過(guò)來(lái)是風(fēng)味維度,我個(gè)人理解為特點(diǎn)維度。
為啥把productFlavors放在下面講,因?yàn)?productFlavors和flavorDimensions經(jīng)常一起使用。
2.4 productFlavors
在我看來(lái)他就是 Gradle 的多渠道打包,你可以在不同的包定義不同的變量,實(shí)現(xiàn)自己的定制化版本的需求。
比如設(shè)置不同的包名、應(yīng)用名等。場(chǎng)景:當(dāng)我們使用友盟統(tǒng)計(jì)時(shí),通常需要設(shè)置一個(gè)渠道 ID ,那么我們就可以利用 productFlavors 來(lái)生成對(duì)應(yīng)渠道信息的包,如:
android {
productFlavors {
wandoujia {
//豌豆莢渠道包配置
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "wandoujia"]
//manifestPlaceholders的使用在后續(xù)章節(jié)(AndroidManifest里的占位符)中介紹
}
xiaomi {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "xiaomi"]
applicationId "com.wiky.gradle.xiaomi" //配置包名
}
_360 {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "_360"]
}
//...
}
}
當(dāng)然也有更簡(jiǎn)潔的方式:
android {
productFlavors {
wandoujia {}
xiaomi {}
_360 {}
//...
}
productFlavors.all {
//批量修改,類似一個(gè)循序遍歷
flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
}
}
-
manifestPlaceholders
占位符,我們可以通過(guò)它動(dòng)態(tài)配置AndroidManifest文件一些內(nèi)容,譬如app的名字:

看看上圖,我們就能發(fā)現(xiàn)我們?cè)?productFlavors 中定義 manifestPlaceholders = [APP_NAME: "(測(cè)試)"] 之后,在 AndroidManifest 的 label 加上 "${APP_NAME}",我們就能控制每個(gè)包打出來(lái)的名字是我們想要不同的名字,譬如測(cè)試服務(wù)器和生產(chǎn)服務(wù)器的包應(yīng)該名字不一樣。
- **dimension **
2.5 buildTypes{}
buildTypes {// 生產(chǎn)/測(cè)試環(huán)境配置
release {// 生產(chǎn)環(huán)境
buildConfigField("boolean", "LOG_DEBUG", "false")//配置Log日志
buildConfigField("String", "URL_PERFIX", "\"https://release.cn/\"")// 配置URL前綴
minifyEnabled false//是否對(duì)代碼進(jìn)行混淆
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'//指定混淆的規(guī)則文件
signingConfig signingConfigs.release//設(shè)置簽名信息
pseudoLocalesEnabled false//是否在APK中生成偽語(yǔ)言環(huán)境,幫助國(guó)際化的東西,一般使用的不多
zipAlignEnabled true//是否對(duì)APK包執(zhí)行ZIP對(duì)齊優(yōu)化,減小zip體積,增加運(yùn)行效率
applicationIdSuffix 'test'//在applicationId 中添加了一個(gè)后綴,一般使用的不多
versionNameSuffix 'test'//在applicationId 中添加了一個(gè)后綴,一般使用的不多
}
debug {// 測(cè)試環(huán)境
buildConfigField("boolean", "LOG_DEBUG", "true")//配置Log日志
buildConfigField("String", "URL_PERFIX", "\"https://test.com/\"")// 配置URL前綴
minifyEnabled false//是否對(duì)代碼進(jìn)行混淆
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'//指定混淆的規(guī)則文件
signingConfig signingConfigs.debug//設(shè)置簽名信息
debuggable false//是否支持?jǐn)帱c(diǎn)調(diào)試
jniDebuggable false//是否可以調(diào)試NDK代碼
renderscriptDebuggable false//是否開(kāi)啟渲染腳本就是一些c寫的渲染方法
zipAlignEnabled true//是否對(duì)APK包執(zhí)行ZIP對(duì)齊優(yōu)化,減小zip體積,增加運(yùn)行效率
pseudoLocalesEnabled false//是否在APK中生成偽語(yǔ)言環(huán)境,幫助國(guó)際化的東西,一般使用的不多
applicationIdSuffix 'test'//在applicationId 中添加了一個(gè)后綴,一般使用的不多
versionNameSuffix 'test'//在applicationId 中添加了一個(gè)后綴,一般使用的不多
}
}
構(gòu)建類型,在 Android Gradle 工程中,它已經(jīng)幫我們內(nèi)置了 debug 和 release 兩個(gè)構(gòu)建類型,兩種模式主要車別在于,能否在設(shè)備上調(diào)試以及簽名不一樣,其他代碼和文件資源都是一樣的。一般用在代碼混淆,而指定的混淆文件在下圖的目錄上,minifyEnabled=true 就會(huì)開(kāi)啟混淆:

常用關(guān)鍵字:
| 關(guān)鍵字 | 解釋 |
|---|---|
| buildConfigField | 自定義函數(shù)變量 |
| applicationIdSuffix | 應(yīng)用id后綴 |
| versionNameSuffix | 版本名稱后綴 |
| debuggable | 是否生成一個(gè)debug的apk |
| minifyEnabled | 是否混淆 |
| proguardFiles | 混淆文件 |
| signingConfig | 簽名配置 |
| manifestPlaceholders | 是否去除未利用的資源,默認(rèn)false,表示不去除 |
| zipAlignEnable | 是否使用zipalign工具壓縮 |
| multiDexEnabled | 是否拆成多個(gè)Dex |
| multiDexKeepFile | 指定文本文件編譯進(jìn)主Dex文件中 |
| multiDexKeepProguard | 指定混淆文件編譯進(jìn)主Dex文件中 |
2.6 signingConfigs
簽名配置,一個(gè) app 只有在簽名之后才能被發(fā)布、安裝、使用,簽名是保護(hù) app 的方式,標(biāo)記該 app 的唯一性。如果 app 被惡意刪改,簽名就不一樣了,無(wú)法升級(jí)安裝,一定程度保護(hù)了我們的 app。而 signingConfigs 就很方便為我們提供這個(gè)簽名的配置。storeFile 簽名文件,storePassword 簽名證書(shū)文件的密碼,storeType 簽名證書(shū)類型,keyAlias 簽名證書(shū)中秘鑰別名,keyPassword 簽名證書(shū)中改密鑰的密碼。

默認(rèn)情況下,debug 模式的簽名已經(jīng)被配置好了,使用的就是 Android SDK 自動(dòng)生成的 debug 證書(shū),它一般位于$HOME/.android/debug.keystore,其 key 和密碼是已經(jīng)知道的,一般情況下我們不需要單獨(dú)配置 debug 模式的簽名信息。

2.7 dexOptions{}
我們知道,Android 中的 Java 源代碼被編譯成 class 字節(jié)碼后,在打包成 apk 的時(shí)候
被dx命令優(yōu)化成 Android 虛擬機(jī)可執(zhí)行的 DEX 文件。
DEX 文件比較緊湊,Android 費(fèi)盡心思做了這個(gè) DEX 格式,就是為了能使我們的程序在 Android 中平臺(tái)上運(yùn)行快一些。對(duì)于這些生成 DEX 文件的過(guò)程和處理,Android Gradle 插件都幫我們處理好了,Android Gradle 插件會(huì)調(diào)用 SDK 中的 dx 命令進(jìn)行處理。
但是有的時(shí)候可能會(huì)遇到提示內(nèi)存不足的錯(cuò)誤,大致提示異常是 java,lang.OutOfMemoryError: GC overhead limit exceeded,為什么會(huì)提示內(nèi)存不足呢?
其實(shí)這個(gè) dx 命令只是一個(gè)腳本,它調(diào)用的還是 Java 編寫的 dx.jar 庫(kù),是 Java 程序處理的,所以當(dāng)內(nèi)存不足的時(shí)候,我們會(huì)看到這個(gè) Java 異常信息.默認(rèn)情況下給 dx 分配的內(nèi)存是一個(gè) G8 ,也就是 1024MB。
所以我們只需要把內(nèi)存設(shè)置大一點(diǎn),就可以解決這個(gè)問(wèn)題,如下代碼,我們把內(nèi)存設(shè)置為 4g。
android {
...
dexOptions {
javaMaxHeapSize "4g"
}
...
}
dependencies{}
我們平時(shí)用的最多的大概就這個(gè)了,看下圖:

- 首先第一句
compile fileTree(include: ['.jar'], dir: 'libs')*,這樣配置之后本地libs文件夾下的擴(kuò)展名為jar的都會(huì)被依賴,非常方便。 - 如果你要引入某個(gè)本地
module的話,那么需要用compile project('×××')。 - 如果要引入網(wǎng)上倉(cāng)庫(kù)里面的依賴,我們需要這樣寫
compile group:'com.squareup.okhttp3',name:'okhttp',version:'3.0.1',當(dāng)然這樣是最完整的版本,縮寫就把group、name、version去掉,然后以":"分割即可。

但是到了 gradle3.0 以后 build.gradle 中的依賴默認(rèn)為 implementation ,而不是
之前的 compile 。另外,還有依賴指令 api。
問(wèn)題:gradle 3.0中依賴implementation、api的區(qū)別:
其實(shí) api 跟以前的 compile 沒(méi)什么區(qū)別,將 compile 全部改成 api 是不會(huì)錯(cuò)的;
而 implementation 指令依賴是不會(huì)傳遞的,也就是說(shuō)當(dāng)前引用的第三方庫(kù)僅限于本 module 內(nèi)使
可參考:
android gradle依賴:implementation 和compile的區(qū)別
3. Gradle 實(shí)用技巧
比如:
- Gradle 依賴樹(shù)查詢
- 使用循環(huán)優(yōu)化Gradle依賴管理
- 支持代碼提示的Gradle依賴管理
- Gradle 模塊化
- Library模塊Gradle代碼復(fù)用
- 資源文件分包
- AAR依賴與源碼依賴快速切換