MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Kotlin中的Gradle构建脚本优化技巧

2021-06-074.3k 阅读

一、Gradle 基础回顾

Gradle 是一个基于 Apache Ant 和 Apache Maven 概念的项目自动化构建工具。它使用一种基于 Groovy 的特定领域语言(DSL)来声明项目设置,摒弃了基于 XML 的传统方式。在 Kotlin 项目中,Gradle 用于管理依赖、编译代码、运行测试以及打包发布等操作。

在 Kotlin 项目中,Gradle 构建脚本通常有两个主要文件:build.gradle.kts(适用于 Kotlin DSL)和 settings.gradle.ktssettings.gradle.kts 主要用于定义项目的整体结构,包括包含哪些子项目等。例如:

rootProject.name = "myKotlinProject"
include("app", "library")

上述代码设置了根项目的名称为 myKotlinProject,并包含了 applibrary 两个子项目。

build.gradle.kts 文件则负责具体项目的构建配置,比如依赖管理、编译选项等。下面是一个简单的 build.gradle.kts 示例:

plugins {
    kotlin("jvm") version "1.6.21"
}

group = "com.example"
version = "1.0.0"

repositories {
    mavenCentral()
}

dependencies {
    implementation(kotlin("stdlib-jdk8"))
    testImplementation("junit:junit:4.13.2")
}

这段代码首先应用了 Kotlin JVM 插件,指定了 Kotlin 的版本。接着设置了项目的组和版本,定义了使用 mavenCentral() 作为依赖仓库,并添加了 Kotlin 标准库以及 JUnit 测试依赖。

二、优化依赖管理

2.1 使用版本目录

Gradle 7.0 引入了版本目录(Version Catalogs)的概念,这使得依赖版本管理更加集中和易于维护。在项目根目录下创建一个 libs.versions.toml 文件,用于集中管理所有依赖的版本。例如:

[versions]
kotlin = "1.6.21"
junit = "4.13.2"

[libraries]
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" }
junit = { module = "junit:junit", version.ref = "junit" }

然后在 build.gradle.kts 文件中,可以这样引用:

plugins {
    kotlin("jvm")
}

group = "com.example"
version = "1.0.0"

repositories {
    mavenCentral()
}

dependencies {
    implementation(libs.kotlin-stdlib)
    testImplementation(libs.junit)
}

这样,如果需要更新 Kotlin 或者 JUnit 的版本,只需要在 libs.versions.toml 文件中修改相应的版本号即可,所有使用该依赖的地方会自动更新。

2.2 依赖排除

在引入依赖时,有时候传递性依赖可能会带来版本冲突等问题。可以使用 exclude 关键字来排除不需要的传递性依赖。例如,假设某个库依赖了 commons-logging,而你的项目已经有了更合适的日志框架,不想引入 commons-logging

dependencies {
    implementation("com.example:some-library:1.0.0") {
        exclude(group = "commons-logging")
    }
}

上述代码排除了 com.example:some-library:1.0.0 库传递依赖中的 commons-logging 库。

2.3 按需引入依赖

根据不同的构建变体(如 debugrelease)引入不同的依赖。例如,在 debug 构建变体中引入用于调试的工具库,而在 release 构建变体中不引入:

android {
    buildTypes {
        getByName("debug") {
            dependencies {
                implementation("com.squareup.leakcanary:leakcanary-android:2.8.1")
            }
        }
        getByName("release") {
            // 不引入 leakcanary 依赖
        }
    }
}

这样在 release 构建中就不会包含 leakcanary 库,从而减小 APK 的大小。

三、优化构建脚本结构

3.1 使用函数和扩展

Gradle 构建脚本支持定义函数和扩展,这可以使构建脚本更加模块化和易读。例如,如果你经常需要添加特定的测试依赖,可以定义一个函数:

fun DependencyHandlerScope.addCustomTestDependencies() {
    testImplementation("org.mockito:mockito-core:4.1.0")
    testImplementation("org.hamcrest:hamcrest-library:2.2")
}

dependencies {
    addCustomTestDependencies()
}

这样,每次需要添加这些测试依赖时,只需要调用 addCustomTestDependencies() 函数即可,使构建脚本更加简洁。

3.2 分离配置

对于大型项目,将不同类型的配置分离到不同的文件中可以提高构建脚本的可维护性。例如,可以将所有的依赖配置放到 dependencies.gradle.kts 文件中,然后在主 build.gradle.kts 文件中引入:

// dependencies.gradle.kts
fun Project.configureDependencies() {
    dependencies {
        implementation(kotlin("stdlib-jdk8"))
        testImplementation("junit:junit:4.13.2")
    }
}

// build.gradle.kts
plugins {
    kotlin("jvm") version "1.6.21"
}

group = "com.example"
version = "1.0.0"

repositories {
    mavenCentral()
}

configureDependencies()

通过这种方式,依赖配置部分更加清晰,并且可以在多个项目中复用 dependencies.gradle.kts 文件。

3.3 使用脚本插件

Gradle 支持创建脚本插件,将常用的配置逻辑封装成插件,然后在多个项目中应用。例如,创建一个 customPlugin.gradle.kts 文件:

import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.dependencies

class CustomPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        target.plugins.apply("kotlin")
        target.dependencies {
            implementation(kotlin("stdlib-jdk8"))
        }
    }
}

然后在项目的 build.gradle.kts 文件中应用这个插件:

plugins {
    id("com.example.customPlugin") version "1.0.0"
}

group = "com.example"
version = "1.0.0"

repositories {
    mavenCentral()
}

这样,通过应用自定义插件,项目就自动添加了 Kotlin 插件以及相应的依赖,提高了构建配置的复用性。

四、优化编译过程

4.1 启用增量编译

Gradle 支持增量编译,这意味着只有修改过的文件会被重新编译,大大加快了编译速度。在 Kotlin 项目中,默认情况下已经启用了增量编译。但是,某些情况下可能需要手动配置。例如,在 build.gradle.kts 文件中:

kotlin {
    jvmToolchain(11)
    sourceSets {
        main {
            kotlin {
                compilerOptions {
                    freeCompilerArgs += listOf("-Xincremental")
                }
            }
        }
    }
}

上述代码显式地启用了 Kotlin 的增量编译选项。

4.2 配置编译线程数

可以通过配置编译线程数来充分利用多核 CPU 的性能,加快编译速度。在 gradle.properties 文件中添加以下配置:

org.gradle.parallel=true
org.gradle.daemon=true
org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8

org.gradle.parallel=true 启用并行构建,org.gradle.daemon=true 启用守护进程模式,org.gradle.jvmargs 配置了 Gradle 守护进程的 JVM 参数,增加了堆内存等设置,有助于处理大型项目的编译。

4.3 使用 Kotlin 编译缓存

Kotlin 1.3 引入了编译缓存功能,它可以缓存编译结果,下次编译相同代码时直接使用缓存,加快编译速度。在 build.gradle.kts 文件中启用:

kotlin {
    jvmToolchain(11)
    cache {
        enabled = true
    }
}

启用编译缓存后,Gradle 会在 ~/.gradle/caches/kotlin-build-cache 目录下存储编译缓存数据。

五、优化构建输出

5.1 自定义输出目录

默认情况下,Gradle 将构建输出放在 build 目录下。可以根据项目需求自定义输出目录。例如,在 build.gradle.kts 文件中:

sourceSets {
    main {
        java {
            srcDirs("src/main/java")
        }
        resources {
            srcDirs("src/main/resources")
        }
    }
    test {
        java {
            srcDirs("src/test/java")
        }
        resources {
            srcDirs("src/test/resources")
        }
    }
}

buildDir = file("customBuild")

上述代码将构建目录修改为 customBuild,这样所有的编译输出、测试结果等都会放在这个自定义目录下。

5.2 优化 APK 输出

对于 Android 项目,优化 APK 输出可以减小 APK 的大小。可以使用 shrinkResourcesminifyEnabled 选项。例如:

android {
    buildTypes {
        getByName("release") {
            shrinkResources = true
            minifyEnabled = true
            proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
        }
    }
}

shrinkResources = true 会移除未使用的资源,minifyEnabled = true 启用代码混淆,proguardFiles 指定了混淆规则文件,通过这些设置可以有效减小 APK 的大小。

5.3 控制日志输出

Gradle 的日志输出可以进行精细控制,只输出必要的信息,避免大量无用日志影响构建速度和可读性。在 gradle.properties 文件中设置日志级别:

org.gradle.logging.level=info

可以设置为 quietwarnerror 等不同级别,info 级别会输出基本的构建信息,quiet 级别只输出错误和严重警告,warn 级别输出警告及以上信息,error 级别只输出错误信息。

六、优化多模块项目构建

6.1 配置子项目依赖

在多模块 Kotlin 项目中,合理配置子项目之间的依赖关系很重要。例如,假设项目有一个 app 模块和一个 library 模块,app 模块依赖 library 模块:

// settings.gradle.kts
rootProject.name = "multiModuleProject"
include("app", "library")

// app/build.gradle.kts
dependencies {
    implementation(project(":library"))
}

通过 project(":library") 这样的方式来声明对 library 模块的依赖,确保在构建 app 模块时,library 模块会先被构建。

6.2 并行构建子项目

对于多模块项目,可以启用并行构建子项目来加快构建速度。在 settings.gradle.kts 文件中设置:

enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")

rootProject.name = "multiModuleProject"
include("app", "library", "feature1", "feature2")

dependencyResolutionManagement {
    versionCatalogs {
        create("libs") {
            from(files("libs.versions.toml"))
        }
    }
}

同时在 gradle.properties 文件中确保 org.gradle.parallel=true 配置项存在,这样 Gradle 会并行构建各个子项目,提高整体构建效率。

6.3 配置公共依赖

如果多个子项目需要相同的依赖,可以将这些公共依赖提取到根项目的 build.gradle.kts 文件中,通过 subprojects 配置块来应用。例如:

subprojects {
    plugins {
        kotlin("jvm") version "1.6.21"
    }

    repositories {
        mavenCentral()
    }

    dependencies {
        implementation(kotlin("stdlib-jdk8"))
    }
}

这样每个子项目都会自动应用 Kotlin 插件以及 kotlin-stdlib-jdk8 依赖,避免在每个子项目中重复配置。

七、使用 Gradle 插件进行优化

7.1 Kotlin 插件优化

Kotlin 官方提供的 Gradle 插件有一些可优化的配置选项。例如,可以配置 Kotlin 编译目标版本:

kotlin {
    jvmToolchain(11)
}

上述代码将 Kotlin 编译目标设置为 Java 11,确保生成的字节码与 Java 11 兼容。同时,还可以配置 Kotlin 编译的其他参数,如 compilerOptions 中的各种选项,以优化编译过程。

7.2 Android Gradle 插件优化

对于 Android 项目,Android Gradle 插件也有很多优化点。例如,可以通过 android.buildFeatures 配置来启用或禁用某些构建特性:

android {
    buildFeatures {
        viewBinding = true
        dataBinding = false
    }
}

上述代码启用了 viewBinding 并禁用了 dataBinding,根据项目实际需求合理配置这些特性可以优化构建过程和项目性能。

7.3 其他常用插件优化

一些第三方插件也可以用于优化构建过程。例如,com.github.dcendents:android-maven-gradle-plugin 插件可以帮助将 Android 库发布到 Maven 仓库,在使用该插件时,合理配置其参数可以确保发布过程顺利且高效:

plugins {
    id("com.github.dcendents.android-maven") version "2.1"
}

group = "com.example"
version = "1.0.0"

android {
    // 常规 Android 配置
}

afterEvaluate {
    publishing {
        publications {
            create<MavenPublication>("release") {
                from(components["release"])
            }
        }
    }
}

通过正确配置这些插件,可以提高项目构建、发布等各个环节的效率。

八、持续集成中的 Gradle 构建优化

8.1 缓存依赖

在持续集成(CI)环境中,缓存依赖可以显著加快构建速度。例如,在 GitHub Actions 中,可以使用 actions/cache 来缓存 Gradle 依赖:

- name: Cache Gradle dependencies
  uses: actions/cache@v2
  with:
    path: ~/.gradle/caches
    key: ${{ runner.os }}-gradle-${{ hashFiles('build.gradle.kts') }}
    restore-keys: |
      ${{ runner.os }}-gradle-

这样在后续的构建中,如果依赖没有变化,就可以直接从缓存中获取,而不需要重新下载。

8.2 优化构建脚本在 CI 中的运行

在 CI 环境中,由于资源有限,需要对构建脚本进行进一步优化。例如,可以减少不必要的日志输出,只保留关键信息。同时,可以根据 CI 环境的特点,调整编译线程数等配置。例如,在 .gitlab-ci.yml 文件中:

image: gradle:7.3.3-jdk11

stages:
  - build

build:
  stage: build
  script:
    - export GRADLE_OPTS="-Dorg.gradle.parallel=true -Dorg.gradle.daemon=true -Dorg.gradle.logging.level=warn"
    - gradle build

通过设置 GRADLE_OPTS 环境变量,在 CI 环境中启用并行构建、守护进程模式,并将日志级别设置为 warn,以提高构建效率。

8.3 利用 CI 特定优化

不同的 CI 平台可能有特定的优化方式。例如,在 CircleCI 中,可以利用其提供的资源配置选项来优化 Gradle 构建。在 .circleci/config.yml 文件中:

version: 2.1
jobs:
  build:
    docker:
      - image: cimg/openjdk:11.0.13
    steps:
      - checkout
      - run: gradle build -Dorg.gradle.jvmargs="-Xmx4096m"

通过为 Gradle 配置更大的堆内存,利用 CircleCI 的资源配置来加快构建速度。

通过以上这些优化技巧,可以在 Kotlin 项目中显著提升 Gradle 构建脚本的性能和效率,无论是在开发过程中,还是在持续集成和发布流程中,都能带来更好的体验。从依赖管理、构建脚本结构、编译过程、构建输出、多模块项目构建,到利用插件以及在持续集成中的优化,每个方面都有可以挖掘和改进的地方,合理运用这些技巧可以使项目的构建更加高效、稳定。