歡迎閱讀 MAD Skills 系列 之 Gradle 與 AGP 構建 API 的第二篇文章。通過上篇文章《Gradle 與 AGP 構建 API: 配置您的構建檔案》您已經瞭解 Gradle 的基礎知識以及如何配置 Android Gradle Plugin。在本文中,您將學習如何通過編寫您自己的外掛來擴充套件您的構建。如果您更喜歡通過視訊瞭解此內容,請在 此處 檢視。
Android Gradle Plugin 從 7.0 版開始提供穩定的擴充套件點,用於操作變體配置和生成的構建產物。該 API 的一些部分是最近才完成的,因此我將會在本文中使用 7.1 版 AGP (撰寫本文時尚處於 Beta 版)。
Gradle Task
我會從一個全新的專案開始。如果您想要同步學習,可以通過選擇基礎 Activity 模板來建立一個新專案。
讓我們從建立 Task 並列印輸出開始——沒錯,就是 hello world。為此,我會在應用層的 build.gradle.kts 檔案註冊一個新的 Task,並將其命名為 "hello"。
tasks.register("hello"){ }
現在 Task 已經準備就緒,我們可以列印出 "hello" 並加上專案名稱。注意當前 build.gradle.kts
檔案屬於應用模組,所以 project.name
將會是當前模組的名字 "app"。而如果我是用 project.parent?.name
,就會返回專案的名稱。
tasks.register("hello"){
println("Hello " + project.parent?.name)
}
是時候執行該 Task 了。此時檢視 Task 列表,可以看到我的 Task 已經位列其中。
△ 新的 Task 已經列在 Android Studio 的 Gradle 窗格中了
我可以雙擊 hello Task 或通過終端執行此 Task,並在構建輸出中觀察它所列印的 hello 資訊。
△ Task 在構建輸出中列印的 hello 資訊
在檢視日誌時,我可以看到此資訊是在配置階段列印的。配置階段實際上與執行 Task 的功能 (例如本例中的列印 Hello World) 無關。配置階段是進行 Task 配置以作用於其執行的階段。您可以在此階段確定 Task 的輸入、引數,以及輸出的位置。
無論請求執行哪個 Task,配置階段都會執行。在配置階段執行耗時操作會導致較長的配置時間。
Task 的執行應當只在執行階段發生,所以我們需要將列印呼叫移動至執行階段。我可以通過新增 doFirst() 或 doLast() 函式來達到這一目的,二者分別可以在執行階段的開始和結束時列印 hello 訊息。
tasks.register("hello"){
doLast {
println("Hello " + project.parent?.name)
}
}
當我再次執行 Task 時,我可以看到 hello 資訊是在執行階段列印的。
△ 現在 Task 會在執行階段列印 hello 資訊
我的自定義 Task 目前位於 build.gradle.kts
檔案中。新增自定義 Task 到 build.gradle
檔案是建立自定義構建指令碼的方便法門。不過,在我的外掛程式碼變得愈發複雜時,這種方式不利於進行擴充套件。我們建議將自定義 Task 和外掛實現放置於 buildSrc
資料夾。
在 buildSrc 中實現外掛
在編寫更多程式碼前,讓我們將 hello Task 移動至 buildSrc
。我會建立一個新的資料夾,並將其命名為 buildSrc
。接下來,我為外掛專案建立了一個 build.gradle.kts
檔案,這樣 Gradle 就會自動將此資料夾新增至構建。
這是專案根資料夾中的頂層目錄。注意,我並不需要在我的專案中將其新增為模組。Gradle 會自動編譯目錄中的程式碼,並將其加入到您構建指令碼的 classpath 中。
接下來,我建立了一個新的 src 資料夾與一個名為 HelloTask
的類。我將新的類改為 abstract
類,並使其繼承 DefaultTask
。隨後,我會新增一個名為 taskAction
的函式、使用 @TaskAction
註解此函式,並將我自定義的 Task 程式碼遷移至此函式中。
abstract class HelloTask: DefaultTask() {
@TaskAction
fun taskAction() {
println("Hello \"${project.parent?.name}\" from task!")
}
}
現在,我的 Task 已經就緒。我會建立一個新的外掛類,這需要實現 Plugin
型別並覆蓋 apply()
函式。Gradle 會呼叫此函式並傳入 Project
物件。為了註冊 HelloTask
,我需要在 project.tasks
上呼叫 register()
,併為這個新的 Task 命名。
class CustomPlugin: Plugin<Project> {
override fun apply(project: Project) {
project.tasks.register<HelloTask>("hello")
}
}
此時,我也可以將我的 Task 宣告為依賴其他 Task。
class CustomPlugin: Plugin<Project> {
override fun apply(project: Project) {
project.tasks.register<HelloTask>("hello"){
dependsOn("build")
}
}
}
下面讓我們應用新的外掛。注意,如果我的專案含有多個模組,我也可以通過將此外掛加入其他 build.gradle 檔案來複用它。
plugins {
id ("com.android.application")
id ("org.jetbrains.kotlin.android")
}
apply<CustomPlugin>()
android {
...
}
現在,我會執行 hello Task,並像之前一樣觀察外掛的執行。
./gradlew hello
到目前為止,我已經將我的 Task 移至 buildSrc
,讓我們更進一步,探索新的 Android Gradle Plugin API。AGP 為其構建產物時的生命週期提供了擴充套件點。
在開始學習 Variant API 前,讓我們先了解什麼是 Variant。變體 (variant) 是您應用可以構建的不同版本。假設除了功能完整的應用,您還希望構建一個演示版的應用或用於除錯的內部版本。您還可以針對不同的目標 API 或裝置型別。變體由多個構建型別組合而成,例如 debug 與 release,以及構建指令碼中定義的產品變種。
在您的構建檔案中,使用宣告式 DSL 新增構建型別是完全沒有問題的。不過,在程式碼中以這種方式讓您的外掛影響構建是不可能的,或者說難以使用宣告式語法進行表達。
AGP 通過解析構建指令碼及 android
塊中設定的屬性來啟動構建。新的 Variant API 回撥讓我可以從 androidComponents
擴充套件中新增 finalizeDSL()
回撥。在此回撥中,我可以在 DSL 物件應用於 Variant 建立前對它們進行修改。我將建立一個新的構建型別並且設定它的屬性。
val extension = project.extensions.getByName(
"androidComponents"
) as ApplicationAndroidComponentsExtension
extension.finalizeDsl { ext->
ext.buildTypes.create("staging").let { buildType ->
buildType.initWith(ext.buildTypes.getByName("debug"))
buildType.manifestPlaceholders["hostName"] = "example.com"
buildType.applicationIdSuffix = ".debugStaging"
}
}
注意,在此階段中,我可以建立或註冊新的構建型別並設定它們的屬性。在階段結束時,AGP 將會鎖定 DSL 物件,這樣它們就無法再被更改。如果我再次執行構建,我會看到應用的 staging 版本被構建了。
現在,假設我的一個測試沒有通過,這時我想要禁用單元測試來構建一個內部版本,以找出問題所在。
為了禁用單元測試,我可以使用 beforeVariants()
回撥。該回撥可以讓我通過 VariantBuilder
物件進行這類修改。在這裡,我會檢查當前變體是否是我為 staging
建立的變體。接下來,我將禁用單元測試並設定不同的 minSdk
版本。
extension.beforeVariants { variantBuilder ->
if (variantBuilder.name == "staging") {
variantBuilder.enableUnitTest = false
variantBuilder.minSdk = 23
}
}
在此階段後,元件列表和將要建立產物都會被確定。
本示例的完整程式碼如下。如需更多此類示例,請查閱 Github gradle-recipes 倉庫:
import com.android.build.api.variant.ApplicationAndroidComponentsExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
class CustomPlugin: Plugin<Project> {
override fun apply(project: Project) {
project.tasks.register("hello"){ task->
task.doLast {
println("Hello " + project.parent?.name)
}
}
val extension = project.extensions.getByName("androidComponents") as ApplicationAndroidComponentsExtension
extension.beforeVariants { variantBuilder ->
if (variantBuilder.name == "staging") {
variantBuilder.enableUnitTest = false
variantBuilder.minSdk = 23
}
}
extension.finalizeDsl { ext->
ext.buildTypes.create("staging").let { buildType ->
buildType.initWith(ext.buildTypes.getByName("debug"))
buildType.manifestPlaceholders["hostName"] = "internal.example.com"
buildType.applicationIdSuffix = ".debugStaging"
// 在後面解釋 beforeVariants 時新增了本行程式碼。
buildType.isDebuggable = true
}
}
}
}
總結
編寫您自己的外掛,您可以擴充套件 Android Gradle Plugin 並根據您的專案需求自定義您的構建!
在本文中,您已經瞭解瞭如何使用新的 Variant API 來在 AndroidComponentsExtension
中註冊回撥、使用 DSL 物件初始化 Variant、影響已被建立的 Variant,以及在 beforeVariants()
中它們的屬性。
在下一篇文章中,我們將進一步介紹 Artifacts API,並向您展示如何從您的自定義 Task 中讀取和轉換產物。
歡迎您 點選這裡 向我們提交反饋,或分享您喜歡的內容、發現的問題。您的反饋對我們非常重要,感謝您的支援!