Gradle遷移指南:從Groovy到KTS
theme: smartblue
Android Gradle 外掛 4.0 支援在 Gradle 構建配置中使用 Kotlin 指令碼 (KTS),用於替代 Groovy(過去在 Gradle 配置檔案中使用的程式語言)。
將來,KTS 會比 Groovy 更適合用於編寫 Gradle 指令碼,因為採用 Kotlin 編寫的程式碼可讀性更高,並且 Kotlin 提供了更好的編譯時檢查和 IDE 支援
。
雖然與 Groovy 相比,KTS 當前能更好地在 Android Studio 的程式碼編輯器中整合,但採用 KTS 的構建速度往往比採用 Groovy 慢,因此在遷移到 KTS 時應考慮構建效能
。
常用術語
KTS:指Kotlin指令碼,是Gradle在構建配置檔案中使用的一種 Kotlin 語言形式。Kotlin 指令碼是可從命令列執行的 Kotlin 程式碼。
Kotlin DSL:主要是指 Android Gradle 外掛 Kotlin DSL,有時也指底層 Gradle Kotlin DSL。
檔案命名
- 用 Groovy 編寫的 Gradle build 檔案使用
.gradle
副檔名 - 用 Kotlin 編寫的 Gradle build 檔案使用
.gradle.kts
副檔名
遷移思路
Groovy 的語法和 Kotlin 的語法雖然相差不小,但在 Gradle DSL 的設計上,還是儘可能保持了統一性,這顯然也是為了降低大家的學習和遷移成本。正因為如此,儘管我們還是要對兩門語言的一些語法細節進行批量處理,遷移過程實際上並不複雜。
處理字串字面量
主要修改點在於 settings.gradle
以及幾個 build.gradle
。
在 Groovy
中,單引號引起來的也是字串字面量,因此我們會面對大量這樣的寫法:
groovy
include ':app', ':tdc_core', ':tdc_uicompat', ':tdc_utils'
這在 Kotlin
中是不允許的,因此需要想辦法將字串字面量單引號統一改為雙引號,可以使用Android Studio的 全域性正則替換
:
- 匹配框輸入正則表示式
'(.*?[^\])'
,替換框中填寫"$1"
,這裡的$1
對應於正則表示式當中的第一個元組,如果有多個元組,可以用$n
來表示,其中$0
表示匹配到的整個字元 - 過濾檔案字尾,我們只對
*.gradle
檔案做替換 - 在檔案字尾後面的漏斗當中選擇 Excepts String literals and Comments,表示我們只匹配程式碼部分
- 在輸入框後面選擇
.*
,藍色高亮表示啟用正則匹配
檢查匹配內容,對匹配錯誤的部分進行修改,點選 Replace All
,所有單引號會變成雙引號:
groovy
include ":app", ":tdc_core", ":tdc_uicompat", ":tdc_utils"
給方法呼叫加上括號
仍以 settings.gradle
為例:
groovy
include ":app", ":tdc_core", ":tdc_uicompat", ":tdc_utils"
此處實際是一個方法呼叫,在 Groovy 中只要沒有歧義,就可以把方法呼叫的括號省略,這在 Kotlin 中是不行的,因此需要統一做加括號處理,採用 全域性正則替換
方法:
- 匹配框輸入正則表示式
(\w+) (([^={\s]+)(.*))
,替換框中填寫$1($2)
,其他配置與前面替換引號一樣
檢查匹配內容,對匹配錯誤的部分進行修改,點選 Replace All
,所有方法呼叫都加上了括號:
groovy
include(":app", ":tdc_core", ":tdc_uicompat", ":tdc_utils")
開始遷移
遷移 settings.gradle
首先,將檔名改為 settings.gradle.kts
, 然後 sync
。
經過前面兩步操作,settings.gradle
內容已經是合法的 Kotlin 程式碼了。
遷移根目錄下的 build.gradle
給檔案增加 .kts
字尾,sync
之後開始解決報錯:
訪問extra擴充套件
過去我們都是通過 ext
訪問 project物件
的動態屬性(參考視訊:Project屬性都是哪裡來的?),Groovy的動態特性支援了這一語法,但Kotlin作為一門靜態語言是不支援的。因此想要訪問ext,就需要使用extra擴充套件,或者 getProperties()["ext"]
,所以:
groovy
ext.kotlin_version = "1.4.30"
等價於
kotlin
extra["kotlin_version"] = "1.4.30"
接下來就是對 kotlin_version
的訪問了,需要將它取出來再使用:
kotlin
val kotlin_version: String by extra
...
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
建立任務
參考 clean
任務的修改方式:
``` kotlin
// 方法一(推薦使用)
tasks.register
// 方法二 task("clean", Delete::class) { delete(rootProject.buildDir) } ``` 在 Groovy 當中 Delete 型別是作為引數通過 Key-Value 的形式傳遞的,Kotlin 當中直接把它當做泛型引數傳入,這樣設計是非常符合 Kotlin 的設計思想。
maven配置
maven語法比較簡單,直接修改為:
kotlin
repositories {
google()
mavenCentral()
maven("http://jitpack.io/")
maven("http://maven.xxx.com/") {
// 信任http協議
isAllowInsecureProtocol = true
}
}
遷移app模組下的 build.gradle
確保頂部 plugins
配置正確,等待IDE建立索引完畢,各個元素就可以訪問了:
語法細節差異,根據程式碼提示修改即可,完整配置參考末尾示例。
顯示和隱式 buildTypes
在 Kotlin DSL 中,某些 buildTypes
(如 debug
和 release,
)是隱式提供的。但是,其他 buildTypes
則必須手動建立。
在 Groovy 中,你可能有 debug
, release
, staging
:
``` groovy buildTypes { debug {
}
release {
}
staging {
}
} ```
在 KTS 中,僅 debug
和release
是隱式提供的,staging
必須手動建立:
``` kotlin buildTypes { getByName("debug") {
}
getByName("release") {
}
create("staging") {
}
} ```
參考示例
settings.gradle.kts
kotlin
include(":app", ":tdc_core", ":tdc_uicompat", ":tdc_utils")
build.gradle.kts
``` kotlin // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { google() mavenCentral() } dependencies { classpath("com.android.tools.build:gradle:7.0.4") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10")
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects { repositories { google() mavenCentral() maven("http://jitpack.io/") maven("http://maven.xxx.com/") { // 信任http協議 isAllowInsecureProtocol = true } } configurations.all { resolutionStrategy.apply { cacheChangingModulesFor(0, "seconds") cacheDynamicVersionsFor(0, "seconds") } } }
tasks.register
app/build.gradle.kts
``` kotlin plugins { id("com.android.application") kotlin("android") }
android { compileSdk = 31
defaultConfig {
applicationId = "com.xx.component"
minSdk = 21
targetSdk = 31
versionCode = 1
versionName = "1.0"
multiDexEnabled = true
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
signingConfigs {
create("keyStore") {
keyAlias = "component"
keyPassword = "123456"
storeFile = file("xx.keystore")
storePassword = "123456"
}
}
buildTypes {
val signConfig = signingConfigs.getByName("keyStore")
getByName("debug") {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
signingConfig = signConfig
}
getByName("release") {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
signingConfig = signConfig
}
create("staging") {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
signingConfig = signConfig
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
viewBinding.isEnabled = true
}
dependencies { implementation("androidx.core:core-ktx:1.7.0") implementation("androidx.appcompat:appcompat:1.4.1")
testImplementation("junit:junit:4.+")
androidTestImplementation("androidx.test.ext:junit:1.1.3")
androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
} ```