【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、相关文档

相关推荐
guoruijun_2012_41 分钟前
安卓 获取 喇叭 听筒 音频输出流 AudioPlaybackCapture API 可以捕获音频输出流
android·音视频
昊虹AI笔记1 小时前
PHP中类名加双冒号的作用
android·java·php
ShanLiang_Build2 小时前
Android 使用Charles抓包显示Unknown
android
逃课的小学生2 小时前
android 安全sdk相关
android
今人不见古时月,今月曾经照古人2 小时前
android studio Terminal控制台命令打包 apk
android·ide·android studio
Winston Wood2 小时前
Android 图形系统之一:概览
android·图形系统
柯南二号2 小时前
Android 实现双列图片瀑布流式布局
android·recycleview·瀑布流
manok3 小时前
Android - 权限管理漏洞
android·sast·静态分析规则
冷眼看人间恩怨3 小时前
【Android】AnimationDrawable帧动画的实现
android·帧动画
所待.3833 小时前
MySQL之视图
android·数据库·mysql