【Gradle-2】一文搞懂Gradle配置

語言: CN / TW / HK

本文為稀土掘金技術社群首發簽約文章,14天內禁止轉載,14天后未獲授權禁止轉載,侵權必究!

1、前言

“Gradle的配置太多了,經常版本更新還有變化,而且它還能擴充套件,記是記不住了,只能用到再搜了,哎,難頂”
真的難頂,但是我想挑戰一下...

本文介紹的重點:

  1. Gradle配置簡介
  2. Gradle中的配置有哪些,都是用來幹什麼的,以及7.0版本之後的變化;
  3. Gradle中的配置怎麼來的;

前置必讀:http://juejin.cn/post/7155109977579847710

2、Gradle配置簡介

Gradle的配置主要是用來管理Gradle自己的執行環境和我們的專案,這句話聽起來有點抽象,用大白話拆解一下:

  1. 第一點其實很好理解,比如Gradle需要java11的執行環境,再比如我們為了加快編譯而給Gradle設定更大的執行空間org.gradle.jvmargs=-Xmx2048m等,這類配置往往相對固定的,因為它是跟隨專案走的,即使是多團隊協作,大家基本也都是用的同一個環境。
  2. 第二點,Gradle作為構建工具,主要是幫助我們編譯打包apk的,apk是由各種檔案組成的,比較多見的是程式碼檔案和資原始檔,那其實可以理解為Gradle本質上是在幫我們管理這些散落在各處的檔案,比如程式碼檔案有app目錄下的原始碼、module、還有依賴的jar和aar等等,而配置可以決定我們依賴哪些程式碼,也可以決定哪些程式碼進入merge,以及打出來的apk產物是release還是debug,但是這類配置往往並不是固定不變的,它是根據開發人員的需求走的,比如提測用debug包,釋出用release包,針對廠商適配可能還要再定製一個渠道包,又或者我需要修改一下版本號等等,這些都是通過配置來改的,具有一定的動態可配性。

2.1、配置的優先順序

為什麼會有優先順序的說法呢,是因為Gradle的配置不僅豐富,而且渠道還不是單一的,這種渠道的不單一,也說明了Gradle優秀的擴充套件性和定製性。

比如我們要執行編譯,既可以用Android Studio自帶的可視快捷按鈕,也可以使用命令列,而這兩種編譯方式的取配置是不一樣的(根據優先順序),那自然編譯的結果也不一樣,所以搞清楚配置的優先順序也能幫助我們在不同場景下的使用需求。

優先順序分以下4種(由高到低):

  1. Command-line flags:命令列標誌,如--stacktrace,這些優先於屬性和環境變數;
  2. System properties:系統屬性,如systemProp.http.proxyHost=somehost.org儲存在gradle.properties檔案中;
  3. Gradle properties:Gradle屬性,如org.gradle.caching=true,通常儲存在專案根目錄或GRADLE_USER_HOME環境變數中的gradle.properties檔案中;
  4. Environment variables:環境變數,如GRADLE_OPTS,由執行Gradle的環境源;

以上4種,我們較常用的是命令列標誌和專案根目錄的gradle.properties檔案。

2.2、專案初始配置

Android Studio Dolphin | 2021.3.1

Gradle 7.4

看一下新建專案之後的初始配置:

有兩個一級目錄,分別是appGradle Scripts,Gradle Scripts裡面除了proguard-rules.pro用於混淆之外,其餘的6個檔案配置今天都會介紹到。

為了把這些檔案的關係看的更直觀一點,列印個tree看看:

. ├── README.md ├── app │   ├── build.gradle │   ├── ... ├── build.gradle ├── gradle │   └── wrapper │   ├── gradle-wrapper.jar │   └── gradle-wrapper.properties ├── gradle.properties ├── local.properties └── settings.gradle

3、Gradle配置

3.1、gradle-wrapper

. ├── gradle │   └── wrapper │   ├── gradle-wrapper.jar │   └── gradle-wrapper.properties ├── gradlew └── gradlew.bat

顧名思義,wrapper是對Gradle的一層封裝,封裝的意義在於可以使Gradle的版本跟著專案走,這樣這個專案就可以很方便的在不同的裝置上執行,比如開源專案一般都不會把gradle資料夾設定到gitignore檔案裡,就是為了保證你clone下來是可以執行的,在團隊協作上也是如此。

下面介紹一下上面這幾個檔案的作用: - gradle-wrapper.jar:主要是Gradle的執行邏輯,包含下載Gradle; - gradle-wrapper.properties:gradle-wrapper的配置檔案,核心是定義了Gradle版本; - gradlew:gradle wrapper的簡稱,linux下的執行指令碼 - gradlew.bat:windows下的執行指令碼

重點看一下gradle-wrapper.properties:

```

Sun Oct 16 15:59:36 CST 2022

distributionBase=GRADLE_USER_HOME distributionUrl=http://services.gradle.org/distributions/gradle-7.4-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME ```

  • distributionBase:下載的Gradle的壓縮包解壓後的主目錄;
  • zipStoreBase:同distributionBase,不過是存放zip壓縮包的主目錄;
  • distributionPath:相對於distributionBase的解壓後的Gradle的路徑,為wrapper/dists;
  • zipStorePath:同distributionPath,不過是存放zip壓縮包的;
  • distributionUrl:Gradle版本的下載地址;

Gradle版本的下載地址,可以檢視Gradle官方的版本釋出,或者去Gradle的Github

這裡有幾種型別,分別是all、bin、doc:

  • doc:顧名思義,使用者文件;
  • bin:即binary,可執行並不包含多餘的東西;
  • all:包含所有,除了bin之外還有使用者文件、sample等;

所以一般選擇bin就可以了。

Gradle、Android Gradle Plugin、Android Studio三者的版本對映關係檢視Android官網Gradle版本說明

AGP > Gradle:

AS > AGP:

3.2、build.gradle(Project)

位於專案的根目錄下,用於定義適用於專案中所有模組的依賴項。

3.2.1、7.0之後

Gradle7.0之後,project下的build.gradle檔案變動很大,預設只有plugin的引用了,其他原有的配置挪到settings.gradle檔案中了。

// Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { id 'com.android.application' version '7.3.0' apply false id 'com.android.library' version '7.3.0' apply false id 'org.jetbrains.kotlin.android' version '1.7.10' apply false }

plugin格式:

id «plugin id» version «plugin version» [apply «false»]

id和version比較好理解。

apply false表示不將該plugin應用於當前專案,比如在多專案構建中,我只想在某個子專案依賴該plugin就好了,那可以這麼寫:

``` plugins { id "yechaoa" version "1.0.0" apply false }

subprojects { subproject -> if (subproject.name == "subProject") { apply plugin: 'yechaoa' } } ```

apply plugin在7.0之前的寫法:

``` apply plugin: 'kotlin-android'

// 加上下面

buildscript { ... dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.20" } } ```

除了自帶外掛和遠端外掛之外,還可以是我們自定義的指令碼外掛,比如自定義了一個common.gradle

apply from: 'common.gradle'

這兩種方式的區別:

  • apply plugin:'yechaoa':叫做二進位制外掛,二進位制外掛一般都是被打包在一個jar裡獨立釋出的,比如我們自定義的外掛,再發布的時候我們也可以為其指定plugin id,這個plugin id最好是一個全限定名稱,就像你的包名一樣;
  • apply from:'yechaoa.gradle':叫做應用指令碼外掛,應用指令碼外掛,其實就是把這個指令碼載入進來,和二進位制外掛不同的是它使用的是from關鍵字,後面緊跟一個指令碼檔案,可以是本地的,也可以是網路存在的,如果是網路上的話要使用HTTP URL
    • 雖然它不是一個真正的外掛,但是不能忽視它的作用,它是指令碼檔案模組化的基礎,我們可以把龐大的指令碼檔案進行分塊、分段整理拆分成一個個共用、職責分明的檔案,然後使用apply from來引用它們,比如我們可以把常用的函式放在一個utils.gradle腳本里,供其他指令碼檔案引用。

後面的文章也會講到外掛的依賴管理。

3.2.1、7.0之前

來看下Gradle7.0之前,project下的build.gradle檔案

``` // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { ext.kotlin_version = "1.5.0" repositories { google() mavenCentral() } dependencies { classpath "com.android.tools.build:gradle:4.2.1" 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 { repositories { google() mavenCentral() jcenter() // Warning: this repository is going to shut down soon } }

task clean(type: Delete) { delete rootProject.buildDir } ```

buildscript

buildscript中的宣告是gradle指令碼自身需要使用的資源。可以宣告的資源包括依賴項、第三方外掛、maven倉庫地址等。7.0之後改在settings.gradle中配置了(pluginManagement)。

多聊一點,為什麼需要buildscript/pluginManagement,因為專案的編譯過程,除了專案本身需要的依賴之外,還有就是Gradle自己執行需要的依賴,比如你的外掛要去hook Gradle的生命週期,因為執行時機的不同,所以加了一個script,當然buildSrc也可以解決這個問題。

ext

專案全域性屬性,多用於自定義,比如把關於版本的資訊都利用ext放在另一個新建的gradle檔案中集中管理,比如version.gradle,然後apply引用即可,這是較為早期的版本管理方式。

repositories

倉庫,比如google()、maven()、jcenter()、jitpack等三方託管平臺。

dependencies

當然配置了倉庫還不夠,我們還需要在dependencies{}裡面的配置裡,把需要配置的依賴用classpath配置上,因為這個dependencies在buildscript{}裡面,所以代表的是Gradle需要的外掛。

7.0之後把plugin的配置簡化了,由apply+classpath簡化為只要apply就可以了。

allprojects

allprojects塊的repositories用於多專案構建,為所有專案提供共同所需依賴包。而子專案可以配置自己的repositories以獲取自己獨需的依賴包。

task clean(type: Delete)

執行gradle clean時,執行此處定義的task。

該任務繼承自Delete,刪除根目錄中的build目錄。相當於執行Delete.delete(rootProject.buildDir)。其實這個任務的執行就是可以刪除生成的Build檔案的,跟Android Studio的clean是一個道理。

3.3、build.gradle(Module)

位於每個 project/module/ 目錄下,用於為其所在的特定模組配置 build 設定。

3.2.1、7.0之後

其實跟7.0之前差別也不是太大。

``` plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' }

android { namespace 'com.yechaoa.gradlex' compileSdk 32

defaultConfig {
    applicationId "com.yechaoa.gradlex"
    minSdk 23
    targetSdk 32
    versionCode 1
    versionName "1.0"

    testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
    release {
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
}
compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
    jvmTarget = '1.8'
}

}

dependencies {

implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

} ```

3.3.1、7.0之前

``` apply plugin: 'com.android.application' apply plugin: 'kotlin-android'

android { compileSdkVersion 30

defaultConfig {
    applicationId "com.yechaoa.app"
    minSdkVersion 19
    targetSdkVersion 30
    versionCode 1
    versionName "1.0"

    testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}

buildTypes {
    release {
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
}

buildFeatures {
    viewBinding = true
}

}

dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

implementation "com.google.android.material:material:1.3.0"

implementation project(':yutils')
implementation project(':yutilskt')

} ```

plugins/apply plugin

前文介紹了,參考 # 3.2、build.gradle(Project)

namespace

應用程式的名稱空間。主要用於訪問應用程式資源。

com.android.application

  • App外掛id:com.android.application
  • Library外掛id:com.android.library
  • Kotlin外掛id:org.jetbrains.kotlin.android

android{}

是Android外掛提供的一個擴充套件型別,可以讓我們自定義Gradle Android工程,是Gradle Android工程配置的唯一入口。

compileSdkVersion/compileSdk

編譯所依賴的Android SDK的版本,即API Level,可以使用此API級別及更低級別中包含的API功能。

api等級對應關係

buildToolsVersion

是構建該Android工程所用構建工具的版本。

後被廢棄了,因為7.0之後,假如你的buildToolsVersion是30.0.1,低於AGP7.0.2所要求的的最低版本30.0.2,那就會使用30.0.2的版本,而不是你指定的版本,後續AGP會應用一個對應的預設的版本。

defaultConfig{}

預設配置,它是一個ProductFlavor。ProductFlavor允許我們根據不同的情況同時生成多個不同的apk包。

applicationId

包名,app的唯一標識。其實他跟AndroidManifest裡面的package是可以不同的,他們之間並沒有直接的關係。

minSdkVersion/minSdk

是支援Android系統的api level,這裡是23,也就是說低於Android 6.0版本的機型不能使用這個app。

targetSdkVersion/targetSdk

是標示app基於哪個Android版本開發的,這裡是32,也就是適配到Android 12.0。

versionCode

版本號,一般用於版本控制。

versionName

版本名稱,公開資訊,預設1.0,7.0之前預設是1.0.0,行業規範3位數,但並沒有強制。

multiDexEnabled

用於配置該BuildType是否啟用自動拆分多個Dex的功能。一般用程式中程式碼太多,超過了65535個方法的時候。

ndk{}

多平臺編譯,生成有so包的時候使用,之前用armeabi的比較多,要求做32/64位適配之後開始拆分(提高效能),'armeabi-v7a'表示32位cpu架構, 'arm64-v8a'表示64位, 'x86'是隻模擬器或特定rom。

一般使用第三方提供的SDK的時候,可能會附帶so庫。

sourceSets

原始碼集合,是Java外掛用來描述和管理原始碼及資源的一個抽象概念,是一個Java原始碼檔案和資原始檔的集合,我們可以通過sourceSets更改源集的Java目錄或者資源目錄等。

buildTypes

構建型別,在Gradle Android工程中,它已經幫我們內建了release構建型別,一般還會加上debug,兩種模式主要區別在於,能否在裝置上除錯以及簽名不一樣,其他程式碼和檔案資源都是一樣的(如果沒有做處理的話-log)。

buildTypes { debug { buildConfigField("String", "AUTHOR", ""yechaoa"") minifyEnabled false } release { buildConfigField("String", "AUTHOR", ""yechaoa"") signingConfig signingConfigs.release shrinkResources true minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } }

  • name:build type的名字
  • applicationIdSuffix:應用id字尾
  • versionNameSuffix:版本名稱字尾
  • debuggable:是否生成一個debug的apk
  • minifyEnabled:是否混淆
  • proguardFiles:混淆檔案
  • signingConfig:簽名配置
  • manifestPlaceholders:清單佔位符
  • shrinkResources:是否去除未利用的資源,預設false,表示不去除。
  • zipAlignEnable:是否使用zipalign工具壓縮。
  • multiDexEnabled:是否拆成多個Dex
  • multiDexKeepFile:指定文字檔案編譯進主Dex檔案中
  • multiDexKeepProguard:指定混淆檔案編譯進主Dex檔案中

signingConfigs

簽名配置,一個app只有在簽名之後才能被髮布、安裝、使用,簽名是保護app的方式,標記該app的唯一性。

signingConfigs { release { keyAlias keystoreProperties['keyAlias'] keyPassword keystoreProperties['keyPassword'] storeFile file(keystoreProperties['storeFile']) storePassword keystoreProperties['storePassword'] v1SigningEnabled true v2SigningEnabled true } }

官方建議不直接明文寫,而是從檔案讀取。

productFlavors

多渠道打包配置,可以實現定製化版本的需求。

buildConfigField

他是BuildConfig檔案的一個函式,而BuildConfig這個類是Android Gradle構建指令碼在編譯後生成的。比如版本號、一些標識位什麼的。

Build Variants

選擇編譯的版本。

buildFeatures

開啟或關閉構建功能,常見的有viewBinding、dataBinding、compose。

buildFeatures { viewBinding = true // dataBinding = true }

dexOptions{}

我們知道,Android中的Java/kotlin原始碼被編譯成class位元組碼後,在打包成apk的時候被dx命令優化成Android虛擬機器可執行的dex檔案。

對於這些生成dex檔案的過程和處理,Android Gradle外掛都幫我們處理好了,Android Gradle外掛會呼叫SDK中的dx命令進行處理。

可以設定編譯時所佔用的記憶體(javaMaxHeapSize),從而提升編譯速度。

7.0之後已經廢棄。

compileOptions

Java編譯選項,指定java環境版本。

kotlinOptions

Kotlin編譯選項,通常指定jvm環境。

composeOptions

Compose功能的可選設定。比如指定Kotlin Compiler版本,一般用預設。

lintOptions

用於配置lint選項。Example

dependencies{}

我們平時用的最多的大概就這個了,Gradle3.2之後有依賴方式的變更,因為不支援依賴關係細粒度的範圍界定。

  • compile > implementation/api
  • androidTestCompile > androidTestImplementation
  • testCompile > testImplementation
  • instrumentTest > androidTest

implementation 'androidx.core:core-ktx:1.7.0'

implementation和api的區別

implementation指令依賴是不會傳遞的,也就是說當前引用的第三方庫僅限於本module內使用,其他module需要重新新增依賴才能用,使用api指令的話可以傳遞。(後面也會細講依賴)

一圖勝千言:

圖源:19snow93

3.4、settings.gradle

位於專案的根目錄下,用於定義專案級程式碼庫設定。

pluginManagement { repositories { gradlePluginPortal() google() mavenCentral() } } dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() } } rootProject.name = "GradleX" include ':app'

pluginManagement

外掛管理,指定外掛下載的倉庫,及版本。

dependencyResolutionManagement

依賴管理,指定依賴庫的倉庫地址,及版本。即7.0之前的allprojects。

順序決定了先從哪個倉庫去找依賴庫並下載,一般為了編譯穩定,會把阿里的映象地址(或自建私有倉庫)放在Google()倉庫之前。

rootProject.name

專案名稱。

include

用於指定構建應用時應將哪些模組包含在內,即參與構建的模組。

也可用於動態引用,在編譯提速時,會module打成aar依賴來節省編譯時間,但是為了開發方便,一般會動態選擇哪些module使用原始碼依賴。

3.5、gradle.properties

位於專案的根目錄下,用於指定 Gradle 構建工具包本身的設定,也可用於專案版本管理。

Gradle本身配置

比如Gradle 守護程式的最大堆大小、編譯快取、並行編譯、是否使用Androidx等等。

```

Project-wide Gradle settings.

IDE (e.g. Android Studio) users:

Gradle settings configured through the IDE will override

any settings specified in this file.

For more details on how to configure your build environment visit

http://www.gradle.org/docs/current/userguide/build_environment.html

Specifies the JVM arguments used for the daemon process.

The setting is particularly useful for tweaking memory settings.

org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m

When configured, Gradle will run in incubating parallel mode.

This option should only be used with decoupled projects. More details, visit

http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects

org.gradle.parallel=true

AndroidX package structure to make it clearer which packages are bundled with the

Android operating system, and which are packaged with your app"s APK

http://developer.android.com/topic/libraries/support-library/androidx-rn

android.useAndroidX=true

Automatically convert third-party libraries to use AndroidX

android.enableJetifier=true

Kotlin code style for this project: "official" or "obsolete":

kotlin.code.style=official

---------- 編譯相關 start ----------

並行編譯

org.gradle.parallel=true

構建快取

org.gradle.caching=true

---------- 編譯相關 end ----------

```

版本管理

```

---------- 版本相關 start ----------

yechaoaPluginVersion="1.0.0"

---------- 版本相關 end ----------

```

在版本管理中,可以從gradle.properties檔案中讀取版本,不僅可以作用於依賴庫,也可作用於依賴外掛。本質上是key-value形式的引數。

pluginManagement { plugins { id 'com.yechaoa.gradlex' version "${yechaoaPluginVersion}" } }

3.6、local.properties

位於專案的根目錄下,用於指定 Gradle 構建配置本地環境屬性,也可用於專案環境管理。

本地配置

```

This file must NOT be checked into Version Control Systems,

as it contains information specific to your local configuration.

Location of the SDK. This is only used by Gradle.

For customization when using a Version Control System, please read the

header note.

Mon Feb 08 19:07:41 CST 2021

sdk.dir=/Users/yechao/Library/Android/sdk ndk.dir=/Users/yechao/Library/Android/ndk ```

  • sdk.dir:SDK 的路徑
  • ndk.dir:NDK 的路徑,已廢棄,用SDK目錄下的NDK目錄。

環境管理

可以用作專案本地除錯的一些開關:

``` isRelease=true

isDebug=false

isH5Debug=false

```

4、Gradle配置怎麼來的

前面介紹了這麼多的Gradle配置,最常用的就是app > build.gradle了,這個檔案裡的配置是最多的,也是跟Android開發打交道最多的,那麼,這些配置是哪裡來的呢?

再來回顧一下app > build.gradle:

``` plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' }

android { namespace 'com.yechaoa.gradlex' compileSdk 32

defaultConfig {
    applicationId "com.yechaoa.gradlex"
    minSdk 23
    targetSdk 32
    versionCode 1
    versionName "1.0"
}

}

dependencies { implementation 'androidx.core:core-ktx:1.7.0' } ```

按結構可以分為3部分,plugins、android和dependencies。

在上一篇入門文章中我們講到,Gradle是一個通用的自動化構建工具,通用也就是說,不僅可以構建Android專案,還可以構建java、kotlin、swift等專案。再結合android{}裡面的配置屬性,我們可以確定,那這就是Android專案專屬的配置DSL了。

既然如此,如果讓你來設計Gradle的架構框架你會怎麼做?

這種架構通常都是底層通用能力上層定製化的經典設計,上層定製化也就是說Android專案有Android的配置DSL,Java專案有Java配置的DSL,不同專案有不同配置。

那麼回到問題本身,android{}是怎麼來的,建立專案就有,那是跟隨開發工具來的?好像也不對啊,Android Studio不僅可以開發Android專案啊,別的也行,而且別的開發工具也能開發Android專案呢。那既然不是跟隨開發工具,又要做到定製化,那是怎麼來的。

回到build.gradle配置結構的第一部分:

plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' }

注意到,這裡有一個名為com.android.application的plugin,看名字也是android專案專屬無疑了,再去原始碼裡看一下這個plugin是幹嘛的。

看Gradle原始碼有個比較簡單的方式,就是直接拉依賴,比如我現在用的Gradle7.4,直接依賴對應版本:

implementation "com.android.tools.build:gradle:7.4"

如果發現還是點不進去原始碼,再把下載Gradle的型別改了,gradle-7.4-bin.zip > gradle-7.4-all.zip。完事重新sync就可以直接點進原始碼了。

先看下android{}的提示:

提煉幾個資訊:

  1. BaseAppModuleExtension
  2. com.android.build.gradle.internal.dsl
  3. Closure
  4. org.gradle.api.Project

這幾個資訊足夠驗證我們之前的猜想了,android{}是通過外掛的方式引入的。

然後點進去:

@HasInternalProtocol public interface Project extends Comparable<Project>, ExtensionAware, PluginAware { ... /** * <p>Configures the dependencies for this project. * * <p>This method executes the given closure against the {@link DependencyHandler} for this project. The {@link * DependencyHandler} is passed to the closure as the closure's delegate. * * <h3>Examples:</h3> * See docs for {@link DependencyHandler} * * @param configureClosure the closure to use to configure the dependencies. */ void dependencies(Closure configureClosure); ... }

是一個dependencies函式,接收一個閉包

再來看BaseAppModuleExtension 的android:

android(Closure configuration)

也是接收一個閉包,這個閉包其實就是android{}這個配置,配置裡面定義的就是我們日常編譯執行需要的配置引數,defaultConfig、minSdk、versionCode之類的。

再看下BaseAppModuleExtension原始碼:

`` /** Theandroid` extension for base feature module (application plugin). */ open class BaseAppModuleExtension( dslServices: DslServices, bootClasspathConfig: BootClasspathConfig, buildOutputs: NamedDomainObjectContainer, sourceSetManager: SourceSetManager, extraModelInfo: ExtraModelInfo, private val publicExtensionImpl: ApplicationExtensionImpl ) : AppExtension( dslServices, bootClasspathConfig, buildOutputs, sourceSetManager, extraModelInfo, true ), InternalApplicationExtension by publicExtensionImpl {

// Overrides to make the parameterized types match, due to BaseExtension being part of
// the previous public API and not wanting to paramerterize that.
override val buildTypes: NamedDomainObjectContainer<BuildType>
    get() = publicExtensionImpl.buildTypes as NamedDomainObjectContainer<BuildType>
override val defaultConfig: DefaultConfig
    get() = publicExtensionImpl.defaultConfig as DefaultConfig
override val productFlavors: NamedDomainObjectContainer<ProductFlavor>
    get() = publicExtensionImpl.productFlavors as NamedDomainObjectContainer<ProductFlavor>
override val sourceSets: NamedDomainObjectContainer<AndroidSourceSet>
    get() = publicExtensionImpl.sourceSets

override val composeOptions: ComposeOptions = publicExtensionImpl.composeOptions

override val bundle: BundleOptions = publicExtensionImpl.bundle as BundleOptions

override val flavorDimensionList: MutableList<String>
    get() = flavorDimensions

override val buildToolsRevision: Revision
    get() = Revision.parseRevision(buildToolsVersion, Revision.Precision.MICRO)

override val libraryRequests: MutableCollection<LibraryRequest>
    get() = publicExtensionImpl.libraryRequests

} ```

我們可以看到一些熟悉的配置,比如buildTypes、defaultConfig、sourceSets等等。不過這只是一部分,剩下的配置在父類AppExtension裡,就不貼程式碼了。

然後android{}這個配置標籤是怎麼創建出來的呢?

``` /* Gradle plugin class for 'application' projects, applied on the base application module / public class AppPlugin extends AbstractAppPlugin<...> {

//...

@NonNull
@Override
protected ExtensionData<...> createExtension(...) {
    // ...

    if (getProjectServices().getProjectOptions().get(BooleanOption.USE_NEW_DSL_INTERFACES)) {
        // noinspection unchecked,rawtypes: Hacks to make the parameterized types make sense
        Class<ApplicationExtension> instanceType = (Class) BaseAppModuleExtension.class;
        BaseAppModuleExtension android =
                (BaseAppModuleExtension)
                        project.getExtensions()
                                .create(
                                        new TypeOf<ApplicationExtension>() {},
                                        "android",
                                        instanceType,
                                        dslServices,
                                        bootClasspathConfig,
                                        buildOutputs,
                                        dslContainers.getSourceSetManager(),
                                        extraModelInfo,
                                        applicationExtension);
        project.getExtensions()
                .add(
                        BaseAppModuleExtension.class,
                        "_internal_legacy_android_extension",
                        android);

        initExtensionFromSettings(applicationExtension);

        return new ExtensionData<>(android, applicationExtension, bootClasspathConfig);
    }

    BaseAppModuleExtension android =
            project.getExtensions()
                    .create(
                            "android",
                            BaseAppModuleExtension.class,
                            dslServices,
                            bootClasspathConfig,
                            buildOutputs,
                            dslContainers.getSourceSetManager(),
                            extraModelInfo,
                            applicationExtension);
    initExtensionFromSettings(android);
    return new ExtensionData<>(android, applicationExtension, bootClasspathConfig);
}
//...

} ```

我把多餘程式碼都刪了,簡而言之,在AppPlugin裡用project.getExtensions().create()方法建立的"android"標籤,AppPlugin即id為'com.android.application'的外掛,跟自定義Plugin一樣配置,後續再展開。

其實所有的DSL配置都是通過外掛的方式引入的。

最後我們再來捋一下思路:

  1. 通過依賴'com.android.application'外掛,有了android{}這個配置DSL;
  2. 一個專案對應一個Project物件,Project物件裡面包含dependencies函式;
  3. android{}這個配置DSL點進去就是Project物件裡面dependencies這個函式,二者都接收一個閉包;
  4. 然後通過DependencyHandler這個類,執行android(Closure configuration)這個閉包並委託給dependencies(Closure configureClosure),也就是Project物件;
  5. 最後Gradle在執行階段去解析這個Project物件並拿到android{}裡面的配置引數,後面就是執行Task等等;

5、總結

我們先是簡單介紹了Gradle的配置,以及配置的優先順序和初始配置等概念,然後針對我們常用的配置進行了詳細的介紹,以及Gradle7.0版本前後的對比,最後通過原始碼分析介紹了Android核心配置是怎麼來的。

原始碼部分對新手可能有點不太友好,閉包、DSL、Plugin這些概念可能暫時還不太好理解,不過不用擔心,後面會繼續講到,先了解下大致流程就行~

寫作不易,點個贊吧!

6、Github

http://github.com/yechaoa/GradleX

7、參考文件