編寫 Gradle 外掛程式

Android Gradle 外掛程式 (AGP) 是 Android 應用程式的官方建構系統。這套系統支援編譯多種不同類型的來源,並連結成為應用程式,供您在實體 Android 裝置或模擬器上執行。

AGP 包含外掛程式的擴充點,用於控制建構輸入,也能透過與標準建構任務整合的新步驟以擴充其功能。舊版 AGP 沒有能與內部實作清楚區分的官方 API。從 7.0 版本開始,AGP 提供一組官方穩定 API,為您提供可靠服務。

AGP API 生命週期

AGP 遵循 Gradle 功能生命週期,以標示其 API 的狀態:

  • Internal:不開放公開使用
  • Incubating:開放公開使用,但非最終版本,也就是說,這個版本可能無法提供最終版本的回溯相容性
  • Public:開放公開使用且功能穩定
  • 「Deprecated」(已淘汰):系統不再支援該 API,已由新的 API 取代

廢止政策

隨著舊版 API 的淘汰,並且替換成全新且穩定的 API 和全新特定領域語言 (DSL),AGP 實現持續的發展。此更新將涵蓋多個 AGP 版本,詳情請參閱 AGP API/DSL 遷移時限

當 AGP API 淘汰後,無論是本次遷移或其他原因,您仍可以在目前的主要版本中繼續使用,但會收到警告。在後續的主要版本中,已淘汰的 API 將從 AGP 中完全移除。例如,如果 API 已在 AGP 7.0 中淘汰,API 仍能在該版本中使用,但會產生警告。而 AGP 8.0 將不再提供此 API。

若要查看常見建構自訂項目的新 API 範例,請參閱 Android Gradle 外掛程式方案。該方案提供常見建構自訂範例。您也可以參閱參考說明文件,進一步瞭解全新 API。

Gradle 建構的基本概念

本指南並未涵蓋全部 Gradle 建構系統。不過,其中涵蓋了與 API 整合所需的最基本概念,並提供主要 Gradle 文件的連結,方便您閱讀。

我們會假設 Gradle 的運作方式,包括如何設定專案、編輯建構檔案、套用外掛程式及執行工作。如要瞭解 Gradle 關於 AGP 的基本知識,建議您參閱設定您的建構項目。如要瞭解自訂 Gradle 外掛程式的一般架構,請參閱「開發自訂 Gradle 外掛程式」一文。

Gradle 延遲類型詞彙表

Gradle 提供許多類型,包括「延遲」運作方式,或為後續建構階段延後重覆運算或創建 Task。這些類型是許多 Gradle 和 AGP API 的核心。下方列出延遲執行的主要 Gradle 類型及其金鑰方法。

Provider<T>
提供 T 類型的值 (其中「T」代表任何類型),可在執行階段使用 get() 讀取,或使用 map()flatMap()zip() 方法轉換為新的 Provider<S> (其中「S」表示其他類型)。請注意,在設定階段不可呼叫 get()
  • map():接受 lambda,產生 SProvider<S> 類型的 Providermap() 的 lambda 引數採用 T 值並產生 S 值。lambda 不會立即執行;而是被延遲到對結果 Provider<S> 呼叫 get() 的時刻,讓整個鏈接延遲完成。
  • flatMap():同時接受 lambda 並產生 Provider<S>,但 lambda 採用 T 並產生 Provider<S> (而非直接產生值 S)。如果在設定時間無法判斷 S,則使用 flatMap(),且您僅能取得 Provider<S>。簡單來說,如果您使用 map() 且最終取得 Provider<Provider<S>> 結果類型,這可能表示您應改用 flatMap()
  • zip():您可合併兩個 Provider 例項,產生一個新 Provider。其中的值是使用函式,合併這兩個輸入的 Providers 例項所提供的值計算得出。
Property<T>
導入 Provider<T>,這樣會提供 T 類型的值。與 Provider<T> (唯讀) 不同,您也可以設定 Property<T> 的值。您可以透過以下兩種方式完成:
  • 如有,請直接設定 T 類型的值,這樣就不必使用延遲計算。
  • 將另一個 Provider<T> 設為 Property<T> 值的來源。在此情況下,只有在呼叫 Property.get() 時,值 T 才能實現。
TaskProvider
實作 Provider<Task>。如要產生 TaskProvider,請使用 tasks.register() 而非 tasks.create(),確保工作只在需要時才延遲執行個體化。您可以在創建 Task 之前使用 flatMap()獲取Task的輸出內容,很適合用來將輸出作為其他 Task 執行個體的輸入。

供應商及其轉換方法以延遲方式設定工作的輸入和輸出內容,也就是不需要預先建立所有工作以及解析值。

提供者也會提供工作依附元件資訊。建立 Provider 方法是轉換 Task 輸出,Task 變成 Provider 隱式依附元件,並且將在解析 Provider 的值時創建並執行,例如當另一個 Task 需要時。

下列是註冊 GitVersionTaskManifestProducerTask 兩項工作的範例,在實際需要前,會延遲建立 Task 執行個體。ManifestProducerTask 輸入值會設為從 GitVersionTask 的輸出內容取得的 Provider,因此 ManifestProducerTask 隱性取決於 GitVersionTask

// Register a task lazily to get its TaskProvider.
val gitVersionProvider: TaskProvider =
    project.tasks.register("gitVersionProvider", GitVersionTask::class.java) {
        it.gitVersionOutputFile.set(
            File(project.buildDir, "intermediates/gitVersionProvider/output")
        )
    }

...

/**
 * Register another task in the configuration block (also executed lazily,
 * only if the task is required).
 */
val manifestProducer =
    project.tasks.register(variant.name + "ManifestProducer", ManifestProducerTask::class.java) {
        /**
         * Connect this task's input (gitInfoFile) to the output of
         * gitVersionProvider.
         */
        it.gitInfoFile.set(gitVersionProvider.flatMap(GitVersionTask::gitVersionOutputFile))
    }

這兩項工作只會在明確要求時執行。Gradle 叫用時會發生此情況,例如您執行 ./gradlew debugManifestProducer,或 ManifestProducerTask 的輸出內容已連結至其他工作,且必須有值時。

雖然您可以編寫會耗用輸入和/或輸出的自訂工作,但 AGP 不會直接提供自己工作的公開存取權。這些實作項目的詳細資料可能因版本不同而改變。相反,AGP 提供 Variant API 並提供工作輸出的存取權或建構成果,方便您讀取及轉換。詳情請參閱本文件中的 Variant API、成果和工作

Gradle 建構階段

建構專案本身相當複雜,是需要資源的過程,且擁有許多功能,例如避免工作設定、最新的檢查項目,以及設定快取功能,協助您盡量縮短可重現或不必要運算的耗時。

Gradle 指令碼和外掛程式必須在各個不同的 Gradle 建構階段 (即初始化、設定和執行) 中遵守嚴格的規則,才能套用其中幾項最佳化。在本指南中,我們將著重說明設定和執行階段。如要進一步瞭解所有階段,請參閱 Gradle 建構生命週期指南

設定階段

在設定階段,系統會評估構成該版本的所有專案的建構指令碼,並套用外掛程式和解決依附元件。這個階段應使用 DSL 物件來設定建構作業,並延遲註冊工作及其輸入內容。

無論要求執行何種工作,設定階段一律都會執行,因此請盡量保持精簡,並根據建構指令碼本身以外的輸入內容限制任何運算作業。換言之,您不應執行外部程式或從網路讀取資料,也不得執行延後至執行階段的長時間運算,做為適當的 Task 執行個體。

執行階段

在執行階段,系統會執行要求的工作及其相依工作。具體來說,系統會執行標示為 @TaskActionTask 類別方法。在工作執行期間,您可以呼叫 Provider<T>.get() 來讀取輸入內容 (例如檔案) 並讀取延遲供應商。透過此方式解決延遲供應商之後,系統會啟動一系列遵循供應商工作依附元件資訊的 map()flatMap() 呼叫。延遲執行工作以實現必要值。

Variant API、成果和工作

Variant API 是 Android Gradle 外掛程式中的擴充功能,可讓您處理各種選項 (通常使用建構設定檔中的 DSL 來設定),這會影響 Android 版本。Variant API 也能讓您存取建構創建的中繼與最終構件,例如類別檔案、合併資訊清單或 APK/AAB 檔案。

Android 建構流程和擴充點

與 AGP 互動時,請使用特製的擴充點,而非註冊一般 Gradle 生命週期回呼 (例如 afterEvaluate()),或設定明確 Task 依附元件。AGP 建立的工作會視為實作詳情,不會以公開 API 的形式顯示。您必須避免嘗試取得 Task 物件的執行個體,或是猜測 Task 名稱,並直接在這些 Task 物件中新增回呼或依附元件。

AGP 會完成下列步驟來建構並執行其 Task 執行個體,如此就會產生建構構件。建立 Variant 物件的主要步驟隨後是回呼,讓您可以在建構作業中變更創建的特定物件。請特別注意,所有回呼都會在設定階段 (如本頁所述) 進行且必須快速執行,同時將任何複雜的工作改為延遲到執行階段,由正確的 Task 例項執行。

  1. DSL 剖析:在評估建構指令碼時,系統會創建和設定 android 區塊的 Android DSL 物件的各種屬性。以下各節將介紹下列 Variant API 回呼。
  2. finalizeDsl():回呼可讓您在變更元件 (變化版本) 鎖定之前變更 DSL 物件。系統會根據 DSL 物件包含的資料建立 VariantBuilder 物件。

  3. DSL 鎖定:DSL 現已鎖定,無法再進行變更。

  4. beforeVariants():此回呼可能會影響透過 VariantBuilder 建立的元件及其屬性。但仍可以修改建構流程和產生的構件。

  5. 建立變化版本:即將建立的元件和成果清單現已確定,且無法變更。

  6. onVariants(): 在這個回呼中,您可以存取已建立的 Variant,您可以為它們包含的 Property 值設定值或供應商,以延遲運算。

  7. 變化版本鎖定:變化版本物件現已鎖定,無法再變更。

  8. 已建立工作:Variant 物件及其 Property 值,用來建立執行建構所需的 Task 執行個體。

AGP 推出了 AndroidComponentsExtension,可讓您註冊 finalizeDsl()beforeVariants()onVariants() 的回呼。可使用 androidComponents 區塊的建構指令碼取得擴充功能:

// This is used only for configuring the Android build through DSL.
android { ... }

// The androidComponents block is separate from the DSL.
androidComponents {
   finalizeDsl { extension ->
      ...
   }
}

不過,建議您僅使用 Android 區塊的 DSL 進行宣告設定,藉此建立版本指令碼,並將任何自訂模擬邏輯移至 buildSrc 或外部外掛程式。此外,您也可以查看 Gradle 方案 GitHub 存放區中的 buildSrc 範例,瞭解如何在專案中建立外掛程式。以下是透過外掛程式的程式碼註冊回呼的範例:

abstract class ExamplePlugin: Plugin<Project> {

    override fun apply(project: Project) {
        val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)
        androidComponents.finalizeDsl { extension ->
            ...
        }
    }
}

以下將進一步說明可用的回呼,以及外掛程式可支援的回呼類型:

finalizeDsl(callback: (DslExtensionT) -> Unit)

在此回呼中,透過剖析構建檔案中 android 區塊的資訊,而建立的 DSL 物件,您可以進行存取及修改。這些 DSL 物件會在後續版本的建構階段中初始化及設定變化版本。例如,您可以程式輔助的方式建立新的設定或覆寫屬性,但請注意,所有值都必須在設定期間完成解析,因此不得仰賴任何外部輸入內容。此回呼完成執行之後,DSL 物件就不再實用,不應再包含參照物件或進行修改。

abstract class ExamplePlugin: Plugin<Project> {

    override fun apply(project: Project) {
        val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)
        androidComponents.finalizeDsl { extension ->
            extension.buildTypes.create("extra").let {
                it.isJniDebuggable = true
            }
        }
    }
}

beforeVariants()

在建構作業的此階段中,您可以存取 VariantBuilder 物件,藉此決定要建立的變化版本和屬性。例如,您可以透過程式停用某些變化版本、其測試,或僅變更所選變化版本的屬性值 (例如 minSdk)。與 finalizeDsl() 類似,您提供的所有值都必須在設定時間解決,且不得依賴外部輸入。執行完 beforeVariants() 回呼後,您就無法修改 VariantBuilder 物件。

androidComponents {
    beforeVariants { variantBuilder ->
        variantBuilder.minSdk = 23
    }
}

beforeVariants() 回呼可選擇接受一個 VariantSelector,您可以透過 androidComponentsExtension 上的 selector() 方法獲取。您可以使用此名稱,根據名稱、版本類型或變種版本篩選參與回呼叫用的元件。

androidComponents {
    beforeVariants(selector().withName("adfree")) { variantBuilder ->
        variantBuilder.minSdk = 23
    }
}

onVariants()

呼叫 onVariants() 時,AGP 建立的所有構件均已決定,因此您無法再停用。但是,您可以在 Variant 物件中設定 Property 屬性,藉此修改工作使用的部分值。由於僅在執行 AGP 工作時才會解析 Property 值,因此您可以放心地將其從自訂工作轉至供應商,以便執行任何必要的運算,包括從外部輸入讀取檔案或網路等。

// onVariants also supports VariantSelectors:
onVariants(selector().withBuildType("release")) { variant ->
    // Gather the output when we are in single mode (no multi-apk).
    val mainOutput = variant.outputs.single { it.outputType == OutputType.SINGLE }

    // Create version code generating task
    val versionCodeTask = project.tasks.register("computeVersionCodeFor${variant.name}", VersionCodeTask::class.java) {
        it.outputFile.set(project.layout.buildDirectory.file("${variant.name}/versionCode.txt"))
    }
    /**
     * Wire version code from the task output.
     * map() will create a lazy provider that:
     * 1. Runs just before the consumer(s), ensuring that the producer
     * (VersionCodeTask) has run and therefore the file is created.
     * 2. Contains task dependency information so that the consumer(s) run after
     * the producer.
     */
    mainOutput.versionCode.set(versionCodeTask.map { it.outputFile.get().asFile.readText().toInt() })
}

將產生的來源提供給建構作業

外掛程式可提供幾種類型的產生來源,例如:

如需可供新增的完整來源清單,請參閱 Sources API 相關說明。

此程式碼片段示範如何使用 addStaticSourceDirectory() 函式,將名為 ${variant.name} 的自訂來源資料夾新增至 Java 來源集。Android 工具鍊會接著處理這個資料夾。

onVariants { variant ->
    variant.sources.java?.let { java ->
        java.addStaticSourceDirectory("custom/src/kotlin/${variant.name}")
    }
}

詳情請參閱「addJavaSource 方案」。

這個程式碼片段示範如何將含有自訂工作所產生 Android 資源的目錄,新增至 res 來源集。這套程序與其他來源類型的程序很類似。

onVariants(selector().withBuildType("release")) { variant ->
    // Step 1. Register the task.
    val resCreationTask =
       project.tasks.register<ResCreatorTask>("create${variant.name}Res")

    // Step 2. Register the task output to the variant-generated source directory.
    variant.sources.res?.addGeneratedSourceDirectory(
       resCreationTask,
       ResCreatorTask::outputDirectory)
    }

...

// Step 3. Define the task.
abstract class ResCreatorTask: DefaultTask() {
   @get:OutputFiles
   abstract val outputDirectory: DirectoryProperty

   @TaskAction
   fun taskAction() {
      // Step 4. Generate your resources.
      ...
   }
}

詳情請參閱「addCustomAsset 方案」。

存取及修改構件

除了讓您修改 Variant 物件的簡易屬性以外,AGP 還提供擴充機制,讓您讀取或轉換在建構期間產生的中繼與最終構件。例如,您可以讀取自訂 Task 中的最終合併 AndroidManifest.xml 檔案內容進行分析,也可將自訂 Task 產生的資訊清單檔案完全換成其中的內容。

您可以在 Artifact 類別的參考說明文件中找到目前支援的構件清單。每個成果類型都有以下特定屬性,值得您瞭解:

基數

Artifact 的基數代表其 FileSystemLocation 執行個體數量,或文檔的數量或成果類型的目錄。您可以檢查其父項類別,取得成果的基數資訊:含有單一 FileSystemLocation 的成果為 Artifact.Single 的子類別;具有多個 FileSystemLocation 執行個體的成果會成為 Artifact.Multiple 的子類別。

FileSystemLocation 種類型

您可以通過查看其參數化的 FileSystemLocation 類型來檢查 Artifact 是否代表文檔或目錄,該類型可以是 RegularFileDirectory

支援的操作

每個 Artifact 類別皆可實作下列任何介面來指出支援的操作:

  • Transformable:允許 Artifact 用作 Task 的輸入,對其執行任意轉換,並輸出新版 Artifact
  • Appendable:僅適用於屬於 Artifact.Multiple 子類別的構件。這表示 Artifact 可附加至其他元素,也就是說,自訂 Task 可以建立此 Artifact 類型的新例項並新增至現有清單。
  • Replaceable:僅適用於屬於 Artifact.Single 子類別的構件。替代的 Artifact 可以換成全新的執行個體,做為 Task 的輸出內容。

除了三個成果修改操作之外,每個成果還支援 get() (或 getAll()) 操作,會傳回含有最終成果版本的 Provider (完成所有操作後)。

多個外掛程式可以透過 onVariants() 回呼,將更多構件操作新增至管道,AGP 可確保這些鏈結作業皆正確無誤,確保所有工作能在適當時間執行,正確產生成果並更新。也就是說,當操作透過附加、取代或轉換的方式變更任何輸出內容時,下一個操作會將這些構件的更新版本視為輸入內容,依此類推。

註冊操作的進入點為 Artifacts 類別。下列程式碼片段說明如何透過 onVariants() 回呼的 Variant 物件上存取 Artifacts 的執行個體。

接著,您可以傳遞自訂 TaskProvider 來取得 TaskBasedOperation 物件 (1),並使用其中一個 wiredWith* 方法 (2) 來連接其輸入和輸出。

您需要選擇的確切方法,取決於您想轉換的 Artifact 實作的基數和 FileSystemLocation 類型。

最後,您必須將 Artifact 類型傳遞至一個方法,該方法表示您在回傳的 *OperationRequest 物件上所選的操作,例如 toAppendTo()toTransform()toCreate() (3)。

androidComponents.onVariants { variant ->
    val manifestUpdater = // Custom task that will be used for the transform.
            project.tasks.register(variant.name + "ManifestUpdater", ManifestTransformerTask::class.java) {
                it.gitInfoFile.set(gitVersionProvider.flatMap(GitVersionTask::gitVersionOutputFile))
            }
    // (1) Register the TaskProvider w.
    val variant.artifacts.use(manifestUpdater)
         // (2) Connect the input and output files.
        .wiredWithFiles(
            ManifestTransformerTask::mergedManifest,
            ManifestTransformerTask::updatedManifest)
        // (3) Indicate the artifact and operation type.
        .toTransform(SingleArtifact.MERGED_MANIFEST)
}

在這個範例中,MERGED_MANIFESTSingleArtifact,且是 RegularFile。因此,我們需要使用 wiredWithFiles 方法,接受輸入的單一 RegularFileProperty 參照,以及接受輸出的單一 RegularFilePropertyTaskBasedOperation 類別中的其他 wiredWith* 方法可搭配其他 Artifact 基數和 FileSystemLocation 類型組合使用。

如要進一步瞭解如何擴充 AGP,建議您從 Gradle 建構系統手冊參閱下列章節: