【Gradle-18】从Groovy迁移至Kotlin

本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!

1、前言

目前Android开发中,构建脚本语言有两种,分别是Groovy DSLKotlin DSL

而在前面的系列文章中,Gradle配置的示例代码都是使用Groovy编写的,也算是有意而为之吧,目的就是为这篇迁移文章做铺垫。

迁移之后两种语言的写法就都涉及到了,可以有更多的选择了,用的时候也不至于因为语法问题难道英雄汉...

2、优劣

Groovy DSL和Kotlin DSL相比,Groovy的运行性能更好,但是Kotlin增加了一些代码补全、编译时检查等开发体验较好的功能。面向开发者来说,显然Kotlin要更友好一些,牺牲一丢丢性能损耗也无伤大雅。

而且随着Kotlin的不断迭代和普及程度,不光在性能上会慢慢追平并超越,用的人也会越来越多。

所以,我认为从Groovy迁移至Kotlin还是很有必要的。

3、迁移

Groovy和Kotlin在构建脚本文件上的区别:

  • Groovy编写的build文件是以.gradle为文件扩展名的,即build.gradle;
  • Kotlin编写的build文件是以.gradle.kts为文件扩展名的,即build.gradle.kts;

除此之外,剩下的就是语法上的区别,不过也不用担心,Gradle是支持混编的,而且本章会以我们常用的settings.gradleapp:build.gradlerootProject:build.gradle为实操,带领大家过一遍迁移细节,后面也可以直接拿来当模版用。

4、settings.gradle

settings.gradle位于项目的根目录下,是用来统一管理插件和依赖的下载地址以及解析策略,还有指定参与源码编译的项目,分别对应着pluginManagement、dependencyResolutionManagement和include/includeBuild。

将settings.gradle,重命名为settings.gradle.kts。

4.1、pluginManagement

Groovy:

ruby 复制代码
pluginManagement {
    repositories {
        gradlePluginPortal()
        google()
        mavenCentral()
        maven {
            // 本地maven仓库地址
            url './plugin/build/maven-repo'
        }
        maven { url 'https://jitpack.io' }
    }
    // 插件解析策略 useVersion/useModule
    resolutionStrategy {
        eachPlugin {
            if (requested.id.id == "com.android.application") {
                //useVersion("2.0")
            }
        }
    }
}

Kotlin:

scss 复制代码
pluginManagement {
    repositories {
        gradlePluginPortal()
        google()
        mavenCentral()
        maven("https://jitpack.io")
        // 本地maven仓库地址
        maven("./plugin/build/maven-repo")
    }
    // 插件解析策略 useVersion/useModule
    resolutionStrategy {
        eachPlugin {
            if (requested.id.id == "com.android.application") {
                //useVersion("2.0")
            }
        }
    }
}

这里主要是maven的写法差异。

将:

rust 复制代码
maven { url 'https://jitpack.io' }

改为:

scss 复制代码
maven("https://jitpack.io")

4.2、dependencyResolutionManagement

Groovy:

ruby 复制代码
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
    repositories {
        google()
        mavenCentral()
        maven {
            // 本地maven仓库地址
            url './plugin/build/maven-repo'
        }
        maven { url 'https://jitpack.io' }

        // 阿里云仓库
        maven { url "https://maven.aliyun.com/nexus/content/repositories/releases" }
        maven { url 'https://maven.aliyun.com/repository/public/' }

        flatDir {
            dirs 'libs'
        }
    }
}

Kotlin:

scss 复制代码
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
    repositories {
        google()
        mavenCentral()
        maven("https://jitpack.io")
        // 本地maven仓库地址
        maven("./plugin/build/maven-repo")

        // 阿里云仓库
        maven("https://maven.aliyun.com/nexus/content/repositories/releases")
        maven("https://maven.aliyun.com/repository/public/")

        flatDir {
            dirs("libs")
        }
    }
}

除了maven的写法差异外,dirs函数的调用必须得是括号,且字符串参数必须是双引号。

4.3、include

Groovy:

php 复制代码
rootProject.name = "GradleX"
include ':app'
include ':plugin'

Kotlin:

php 复制代码
rootProject.name = "GradleX"
include(":app")
include(":plugin")

同上,函数的调用必须得是括号,且字符串参数必须是双引号,includeBuild同理。

4.4、依赖plugin

在原来settings.gradke中有个源码和AAR切换的插件,这里为直接抽到一个UseLocalPlugin.gradle文件中。

typescript 复制代码
apply plugin: UseLocalPlugin

class UseLocalPlugin implements Plugin<Settings> {
    @Override
    void apply(Settings settings) {

        // ...
    }
}

引用:

ini 复制代码
apply(from = "UseLocalPlugin.gradle")

会自动解析并应用插件。

老的依赖方式:

csharp 复制代码
apply from: 'UseLocalPlugin.gradle'

5、rootProject:build.gradle

位于项目的根目录下,通常用来定义全局的插件、仓库和配置。

将build.gradle重命名为build.gradle.kts。

5.1、buildscript

Groovy:

scss 复制代码
buildscript {
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        // GradleX本地仓库地址
//        classpath('com.yechaoa.plugin:gradleX:1.6-SNAPSHOT')
        // GradleX远端仓库地址
        classpath('com.github.yechaoa.GradleX:plugin:1.8')
    }
}

Kotlin:

scss 复制代码
buildscript {
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        // GradleX本地仓库地址
//        classpath("com.yechaoa.plugin:gradleX:1.6-SNAPSHOT")
        // GradleX远端仓库地址
        classpath("com.github.yechaoa.GradleX:plugin:1.8")
    }
}

这里只要把GradleX插件依赖的仓库地址从单引号改成双引号即可。

5.2、plugins

Groovy:

bash 复制代码
plugins {
    id("com.android.application") version '8.1.1' apply false
    id 'com.android.library' version '8.1.1' apply false
    id 'org.jetbrains.kotlin.android' version '1.7.10' apply false
    id "com.dorongold.task-tree" version "2.1.1"
}

Kotlin:

bash 复制代码
plugins {
    id("com.android.application") version "8.1.1" apply false
    id("com.android.library") version "8.1.1" apply false
    id("org.jetbrains.kotlin.android") version "1.7.10" apply false
    id("com.dorongold.task-tree") version "2.1.1"
}

这里主要是把plugin依赖的id调用改成括号,然后version的指定也改成双引号即可。

5.3、task

Groovy:

typescript 复制代码
task clean(type: Delete) {
    delete rootProject.buildDir
}

Kotlin:

scss 复制代码
tasks.create<Delete>("clean") {
    delete(rootProject.buildDir)
}

这个clean的task也可以不要,Clean Project是一样的。

5.4、额外属性

Groovy:

ini 复制代码
ext {
    kotlinVersion = "1.9.20"
}

// or

ext.kotlinVersion = "1.9.20"

Kotlin:

kotlin 复制代码
val kotlinVersion by extra("1.9.20")

6、app:build.gradle

位于每个 project /module/ 目录下,用于指定其模块的build设置,主要包括插件、项目配置和依赖。

将build.gradle重命名为build.gradle.kts。

6.1、plugins

Groovy:

bash 复制代码
plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'com.yechaoa.plugin.gradleX'
}

Kotlin:

bash 复制代码
plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
    id("com.yechaoa.plugin.gradleX")
}

使用括号和双引号。

另外,plugin的依赖方式有两种。

一种是名称直接引用name的方式,即

markdown 复制代码
plugins {
    gradleX
}

另一种就是id的方式,即

bash 复制代码
plugins {
    id("com.yechaoa.plugin.gradleX")
}

推荐大家使用id的方式依赖插件。

6.2、android

6.2.1、namespace

Groovy:

arduino 复制代码
    namespace 'com.yechaoa.gradlex'

Kotlin:

ini 复制代码
    namespace = "com.yechaoa.gradlex"

6.2.2、compileSdk

Groovy:

markdown 复制代码
    compileSdk 33

Kotlin:

ini 复制代码
    compileSdk = 33

6.2.3、defaultConfig

Groovy:

csharp 复制代码
    defaultConfig {
        applicationId "com.yechaoa.gradlex"
        minSdk 23
        targetSdk 33
        versionCode getVersionCodeByProperty()
        versionName getVersionNameByProperty()

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

        ndk {
            //noinspection ChromeOsAbiSupport
            abiFilters 'arm64-v8a', 'armeabi-v7a', 'armeabi-v8a'
        }
    }

Kotlin:

ini 复制代码
    defaultConfig {
        applicationId = "com.yechaoa.gradlex"
        minSdk = 23
        targetSdk = 33
        versionCode = getVersionCodeByProperty()
        versionName = getVersionNameByProperty()

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

        ndk {
            //noinspection ChromeOsAbiSupport
            abiFilters.addAll(mutableSetOf("arm64-v8a", "armeabi-v7a", "armeabi-v8a"))
        }
    }

6.2.4、buildTypes

Groovy:

java 复制代码
    buildTypes {
        debug {
            versionNameSuffix "-测试包"
        }
        release {
            versionNameSuffix "-正式包"
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

Kotlin:

javascript 复制代码
    buildTypes {
        getByName("debug") {
            versionNameSuffix = "-测试包"
        }
        getByName("release") {
            versionNameSuffix = "-正式包"
            isMinifyEnabled = false
            proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
        }
    }

6.2.5、compileOptions

Groovy:

markdown 复制代码
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_17
        targetCompatibility JavaVersion.VERSION_17
    }

Kotlin:

ini 复制代码
compileOptions {
    sourceCompatibility = JavaVersion.VERSION_17
    targetCompatibility = JavaVersion.VERSION_17
}

6.2.6、kotlinOptions

Groovy:

ini 复制代码
    kotlinOptions {
        jvmTarget = '17'
    }

Kotlin:

ini 复制代码
    kotlinOptions {
        jvmTarget = "17"
    }

6.2.7、applicationVariants

Groovy:

arduino 复制代码
    applicationVariants.configureEach { variant ->
        variant.outputs.each { output ->
            if (variant.buildType.name == "debug") {
                output.versionNameOverride = "3.0"
                output.versionCodeOverride = 3
            }
            // 动态删除清单文件里的权限
//            output.processManifest.doLast {
//                // 获取manifest文件
//                def manifestOutFile = output.processResourcesProvider.get().getManifestFile()
//                // 读取manifest文件
//                def manifestContent = manifestOutFile.getText()
//                // 删除指定权限
//                manifestContent = manifestContent.replaceAll('android.permission.RECORD_AUDIO', 'android.permission.INTERNET')
//                // 再写回manifest文件
//                manifestOutFile.write(manifestContent)
//            }
        }
    }

Kotlin:

kotlin 复制代码
    applicationVariants.configureEach {
        val buildTypeName = this.buildType.name
        this.outputs.all {
            val output = this as com.android.build.gradle.api.BaseVariantOutput
            // 动态修改VersionName和VersionCode
            if (buildTypeName == "debug") {
                val apkOutput = this as? com.android.build.gradle.api.ApkVariantOutput
                apkOutput?.let {
                    it.versionCodeOverride = 3
                    it.versionNameOverride = "3.0"
                }
            }
            // 动态删除清单文件里的权限
//            output.processManifest.doLast {
//                // 获取manifest文件
//                val manifestOutFile = output.processResourcesProvider.get().getManifestFile()
//                // 读取manifest文件
//                var manifestContent = manifestOutFile.readText()
//                // 删除指定权限
//                manifestContent = manifestContent.replace("android.permission.RECORD_AUDIO", "android.permission.INTERNET")
//                // 再写回manifest文件
//                manifestOutFile.writeText(manifestContent)
//            }
        }
    }

这里主要是我们在动态修改VersionName和VersionCode的时候要用到versionCodeOverride和versionNameOverride,这倆方法不在BaseVariantOutput中,所以转换成ApkVariantOutput。

另外,多层嵌套this指向不明确的时候

6.2.8、buildFeatures

还有之前未提到的buildFeatures。

Kotlin:

ini 复制代码
    buildFeatures {
        buildConfig = true
        viewBinding = true
    }

6.3、dependencies

Groovy:

css 复制代码
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.aar'])
//    implementation project(':plugin')
    implementation 'androidx.core:core-ktx:1.9.0'
}

Kotlin:

less 复制代码
dependencies {
    implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar", "*.aar"))))
//    implementation(project(":plugin"))
    implementation("androidx.core:core-ktx:1.9.0")
}

6.4、resolutionStrategy

Groovy:

csharp 复制代码
    // 版本冲突解析策略
    resolutionStrategy.eachDependency { DependencyResolveDetails details ->
        def requested = details.requested
        if (requested.group == 'com.squareup.okhttp3' && requested.name == 'okhttp') {
            details.useVersion '4.10.0'
        }
        // 还可以使用startsWith通配,比如room-compiler、room-rxjava2、room-testing
//        if (requested.group == 'androidx.room' && requested.name.startsWith('room')) {
//            details.useVersion '2.5.0'
//        }
    }

Kotlin:

ini 复制代码
    resolutionStrategy.eachDependency {
        val details = this as DependencyResolveDetails
        val requested = details.requested
        if (requested.group == "com.squareup.okhttp3" && requested.name == "okhttp") {
            details.useVersion("4.10.0")
        }
        // 还可以使用startsWith通配,比如room-compiler、room-rxjava2、room-testing
//        if (requested.group == "androidx.room" && requested.name.startsWith("room")) {
//            details.useVersion "2.5.0"
//        }
    }

details的引用在写法上做了一层转换,比直接使用this的写法更易于阅读,也可以lambda指定类型。

或者也可以使用kotlin object匿名内部类的方式:

kotlin 复制代码
    resolutionStrategy.eachDependency(object :Action<DependencyResolveDetails>{
        override fun execute(details: DependencyResolveDetails) {
            val requested = details.requested
            if (requested.group == "com.squareup.okhttp3" && requested.name == "okhttp") {
                details.useVersion("4.10.0")
            }
        }
    })

6.5、substitute

Groovy:

sql 复制代码
resolutionStrategy.dependencySubstitution {
        // 在local.properties中配置useLocal=true,或执行:./gradlew assembleDebug -PuseLocal=true
//        if (useLocal) {
//            // 把yutilskt:3.4.0替换成源码依赖
//            substitute module('com.github.yechaoa.YUtils:yutilskt:3.4.0') using project(':yutilskt')
//        }
        // 把yutilskt替换成远端依赖
//            substitute project(':yutilskt') using module('com.github.yechaoa.YUtils:yutilskt:3.4.0')
        // 把yutilskt:3.3.3替换成yutilskt:3.4.0
//        substitute module('com.github.yechaoa.YUtils:yutilskt:3.3.3') using module('com.github.yechaoa.YUtils:yutilskt:3.4.0')
    }

Kotlin:

sql 复制代码
    resolutionStrategy.dependencySubstitution {
        // 在local.properties中配置useLocal=true,或执行:./gradlew assembleDebug -PuseLocal=true
//        if (useLocal) {
//            // 把yutilskt:3.4.0替换成源码依赖
//            substitute(module("com.github.yechaoa.YUtils:yutilskt:3.4.0")).using(project(":yutilskt"))
//        }
        // 把yutilskt替换成远端依赖
//        substitute(project (":yutilskt")).using(module("com.github.yechaoa.YUtils:yutilskt:3.4.0"))
        // 把yutilskt:3.3.3替换成yutilskt:3.4.0
//        substitute(module("com.github.yechaoa.YUtils:yutilskt:3.3.3")).using(module("com.github.yechaoa.YUtils:yutilskt:3.4.0"))
    }

在kotlin dsl中substitute的写法要连着写,即

less 复制代码
substitute(module("com.github.yechaoa.YUtils:yutilskt:3.4.0")).using(project(":yutilskt"))

6.6、localProperties

Groovy:

scss 复制代码
// 加载local.properties文件
def localProperties = new Properties()
file("../local.properties").withInputStream { localProperties.load(it) }
// 从localProperties中获取useLocal属性
def useLocal = localProperties.getProperty('useLocal', 'false').toBoolean()

Kotlin:

scss 复制代码
// 加载local.properties文件
val localProperties = Properties()
file("../local.properties").inputStream().use { localProperties.load(it) }
// 从localProperties中获取useLocal属性
val useLocal = localProperties.getProperty("useLocal", "false").toBoolean()

7、总结

以上就是从Groovy迁移至Kotlin的主要流程了,更多的大家可以查看github源码

我们来总结一下迁移过程中主要的几个语法差异:

  1. 字符引号:Groovy中字符串可以用单引号'string'或双引号来"string",但是在Kotlin中则必须使用双引号。
  2. 函数调用:Groovy允许在调用函数时省略括号,而Kotlin则是必须显式使用括号。
  3. 属性赋值:比如=号,Groovy允许省略赋值运算符,而Kotlin则必须显式使用赋值运算符。
  4. 额外属性:Groovy的额外属性是使用ext对象,Kotlin中是使用extra对象。

8、GitHub

github.com/yechaoa/Gra...

9、相关文档

相关推荐
阿巴斯甜13 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker13 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952714 小时前
Andorid Google 登录接入文档
android
黄林晴16 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android