使用Version Catalog管理Android依赖


本文由 简悦 SimpRead 转码, 原文地址 juejin.cn

使用 Version Catalog 管理 Android 依赖

前言

前面写了篇《Android 依赖管理实践与总结》,里面关于 Version Catalog 的部分鸽了,最近拿练手项目升级了 gradle7.X,并使用了 kts,正好把 Version Catalog 也实践了下,这里记录下。

版本依赖

Version Catalog 需要 gradle7.x 版本,如果还没升级的话可以先升级下 -_-||。前面 gradle 版本中都是特性,到了 gradle7.4.1 版本好像就稳定版本了。

如果 gradle 版本低于 7.4.1 需要在 setting.gradle.kts 中添加下面代码开启 Version Catalog:

enableFeaturePreview("VERSION_CATALOGS")

如果版本比这更高就不用管它了。这里顺便说个问题,我看好多例子的 setting.gradle.kts 代码里面还有下面这句:

// 开了会报错: 已在类 org.gradle.accessors.dm.RootProjectAccessor中定义了方法 getVersionPlugin()
// 引用本地模块(新版写法,比如“test-library”): implementation(projects.testLibrary)
//enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")

我这发现,加上这句会报异常,类似的 enableFeaturePreview,提一下。

说完版本依赖,说下我下面例子的版本环境:

  • Gradle 7.5.1
  • AGP 7.4.2

下面就开始介绍使用吧。

配置 Version Catalog

上面依赖没问题了,我们就可以在 setting.gradle.kts 的 dependencyResolutionManagement 中去配置 versionCatalogs 了,例子如下:

dependencyResolutionManagement {
    versionCatalogs {
        // 可以创建多个版本目录
        create("libs") {
            // 设置版本: alias + version
            version("groovy", "3.0.5")
            version("checkstyle", "8.37")

            // 设置库: alias + group + articact + version
            library("groovy-core", "org.codehaus.groovy", "groovy").versionRef("groovy")
            library("groovy-json", "org.codehaus.groovy", "groovy-json").versionRef("groovy")
            library("groovy-nio", "org.codehaus.groovy", "groovy-nio").versionRef("groovy")

            // 版本号可以设置成范围的
            library("commons-lang3", "org.apache.commons", "commons-lang3").version {
                strictly("[3.8, 4.0[")
                prefer("3.9")
            }

            // 声明一个依赖组
            bundle("groovy", listOf("groovy-core", "groovy-json", "groovy-nio"))

            // gradle插件的版本
            plugin("versions", "com.github.ben-manes.versions").version("0.45.0")
        }
    }
}

配置好了就可以在 module 中去使用了,不过不是 kotlin 代码而是 build.gradle,下面是上面例子在 app 的 build.gradle 的使用:

dependencies {
    // 引用库
    implementation(libs.groovy.core)
    implementation(libs.groovy.json)
    implementation(libs.groovy.nio)

    // 引用一组库
    implementation(libs.bundles.groovy)
}

// 插件中的使用
plugins {
    `java-library`
    checkstyle
    alias(libs.plugins.versions)
}

效果如图:

上面这些例子都是从 gradle 的官方文档来的,kts 和 groovy 语法可能还不一样,由于版本原因用法也可能不一样,不明白的话可以看下官方文档:

不过,如果只是看下如何配置的话,就没必要写一篇文章了,下面我们来继续看看 Version Catalog 如何通过文件和插件进行配置。

使用文件配置

Android 官方文档里面推荐我们在项目根目录下面的 gradle 目录里面创建一个 libs.versions.toml 来进行配置 Version Catalog,案例如下:

[versions]
ktx = "1.9.0"

[libraries]
androidx-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "ktx" }

配置好了,我们就可以直接用了,不需要导入,gradle 会自动操作:

dependencies {
   implementation(libs.androidx.ktx)
}

官方文档要求目录和文件名都不修改,用起来还是挺简单的,但是我们也是可以自定义的。

下面在根目录下新建一个 libs.toml 文件 (文件名任意),配置同样的东西,我们手动导入下,使用方式还是一样:

dependencyResolutionManagement {
    versionCatalogs {
        create("libs") {
            // 从文件中导入,files别写错了
            from(files("$rootDir/libs.toml"))
        }
    }
}

关于 TOML 文件

TOML 文件里面的写法实际和我们在前面手动配置的类似,它主要由 4 个部分组成

  • [versions] 用于声明可以被依赖项引用的版本
  • [libraries] 用于声明依赖的别名
  • [bundles] 用于声明依赖包(依赖组)
  • [plugins] 用于声明插件

里面的节点不能随便定义,只能是 versions、libraries、bundles、plugins、metadata 中的一个,下面给个全一点的例子:

[versions]
# 编译版本
compileSdkVersion = "31"
minSdkVersion = "19"
targetSdkVersion = "30"
versionCode = "1"
versionName = "1.0"

[libraries]
# 基本库
kotlin = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin"}
core_ktx = { module = "androidx.core:core-ktx", version.ref = "core_ktx"}
appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat"}
material = { module = "com.google.android.material:material", version.ref = "material"}
constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayout"}

[bundles]
versionBase = [
    # kotlin
    "kotlin", "core_ktx", "appcompat", "material", "constraintlayout",

    # 协程
    "kotlinx_coroutines_core", "kotlinx_coroutines_android",

    # Jetpack lifecycle
    "lifecycle_extensions", "lifecycle_viewmodel_ktx", "lifecycle_livedata_ktx"
]

[plugins]

编译版本使用要稍微注意下,需要做个转换,我试了直接 poml 里面设置 int 型好像不太行:

android {
    compileSdk = libs.versions.compileSdkVersion.get().toInt()
    defaultConfig {
        applicationId = "com.silencefly96.fundark"
        minSdk  = libs.versions.minSdkVersion.get().toInt()
        targetSdk  = libs.versions.targetSdkVersion.get().toInt()
        versionCode = libs.versions.versionCode.get().toInt()
        versionName = libs.versions.versionName.get()
    }
}

在文件里面可以通过 “#” 进行注释,整个文件通过节点分成几部分,可以有空行,但是 “=” 号不可以换行,数组里面带逗号倒是可以换行。

我也是通过我 Dependencies.kt 改过来的,可以配合 ctrl + r 来处理这些繁琐的语法替换。

Version Catalog 插件使用

我看很多文章也有提到插件的使用,但是给的例子却有点让人摸不着头脑,我这前面文章正好写了插件的使用,稍微改了改,把 Version Catalog 插件发布到本地,给其他项目使用,希望对读者有所帮助。

首先我们要明白下整个过程的逻辑,搞这个 Version Catalog 插件是干什么,我理解啊,就是我们把通用的 Version Catalog 配置打包成一个 gradle 插件,然后其他项目再引入这个插件,那样这些个项目的版本就能保持一致,不容易冲突。

也就是说,我们得先有个用来发布 gradle 插件的项目,在这打包好 gradle 插件,然后还要有用来引入这个插件的项目,理解好了这些,我们的目的就很明确了。

打包插件

首先我们就得搞个项目来打包插件,可以新建个项目 (也可以是 module,项目更好理解),然后在它的 app 模块的 build.gradle 配置两个插件:

plugins {
    id("version-catalog")
    id("maven-publish")
}

其中 maven-publish 是用来发布到 maven 仓库的,version-catalog 是用来把 Version Catalog 配置放到插件中的。

配置好上面两个插件,下面就来配置需要打包的 Version Catalog 配置:

catalog {
    versionCatalog {
        // 从文件中导入
        from(files("./gradle/libs.versions.toml"))

        // 直接配置
        version("groovy", "3.0.5")
        version("checkstyle", "8.37")
        library("groovy-core", "org.codehaus.groovy", "groovy").versionRef("groovy")
        library("groovy-json", "org.codehaus.groovy", "groovy-json").versionRef("groovy")
    }
}

和引入 Version Catalog 类似,不过不要理解成了引入,配置方法可以直接配置,也可以从文件中导入。

将要打包进插件的 Version Catalog 配置弄好后就是发布插件了,同样在 app 模块的 build.gradle 中添加:

// 这两个是我项目的配置,不写会出错,可以适当参考下,我是觉得不用
group = "silencefly96.catalog"
version = "1.0.0"

// 发布的task
publishing{
    publications {
        // 会新建一个catalog-plugin目录
        create<MavenPublication>("catalog-plugin") {
            // 配置信息,使用: classpath("groupId:artifactId:version"(不能有空格))
            groupId = "silencefly96.catalog"
            artifactId = "catalog-plugin"
            version = "1.0.0"
            from(components["versionCatalog"])
        }
    }
    repositories {
        // 本地的 Maven(地址设置,部署到本地,也就是项目的根目录下)
        maven { url = uri("../catalog_repo") }
    }
}

其实这里就是 maven-publish 插件 (老版本还是叫 maven) 的使用了,kts 和 groovy 语法差异有点大,gradle7.x 的用法和之前也不一样,可以查查资料看下你的版本 maven-publish 插件如何用。

关于插件可以看下我这篇文章: Gradle 自定义插件实践与总结

写好后就可以执行 gradle task 了,在侧边栏 gradle 中找到 publishing 执行,就能发现在项目的根目录生成了一个 catalog_repo 目录,里面放的就是我们的 Version Catalog 插件了。

使用插件

生成好插件了,我们就能在其他项目引入它了,引入的方式和上面差不多,在需要引入项目的 setting.gradle.kts 中配置:

dependencyResolutionManagement {
    repositories {
        // 引入本地仓库依赖
        maven{ url = uri("./catalog_repo") }

        // 其他依赖
    }

    versionCatalogs {
        create("libs") {
            // 从 maven 仓库获取依赖
            from("silencefly96.catalog:catalog-plugin:1.0.0")

            // 其他配置
        }
    }
}

这里需要两步,第一步就是在 repositories 引入本地 maven 仓库,第二个就是从刚刚我们打包好的插件中导入 Version Catalog 配置。

导入后,sync 一下,在需要使用的 module 中我们就可以使用依赖了:

插件优化

之前看别人写的插件,可以简化 module 中 build.gradle 的编写,我这也正好实践了下,配合 Version Catalog 一起使用。

添加依赖

编写优化 gradle 的插件,首先要引入 gradle 相关的 api,在 module 的 build.gradle 中添加:

// 插件的依赖关系
dependencies {
    implementation(gradleApi())
    implementation("com.android.tools.build:gradle:7.4.2")
    implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.0")
}

注意这里引入的 gradle 以及 kotlin 插件是给我们代码用的,所以并不是 classpath 引入给 buildscript 用的,要区分开来。

编写插件

接下来就是编写插件了,这里我用的和 Composing build 一样的方式,可以先看下我之前的文章:

Android 依赖管理实践与总结

或者直接参考别人的源文章:

是时候弃用 buildSrc , 使用 Composing builds 加快编译速度了

下面就是我写的插件代码了:

package com.silencefly96.plugins

import com.android.build.api.dsl.ApplicationExtension
import com.android.build.api.dsl.CommonExtension
import org.gradle.api.JavaVersion
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.ExtensionAware
import org.gradle.kotlin.dsl.configure
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions

class ApplicationOptimizePlugin : Plugin<Project> {

    @Suppress("UnstableApiUsage")
    override fun apply(target: Project) {
        // 配置几个个基础插件
        with(target.plugins) {
            apply("com.android.application")
            apply("kotlin-android")
            apply("kotlin-kapt")
            apply("kotlin-parcelize")
        }

        // 配置android闭包
        target.extensions.configure<ApplicationExtension> {

            // compileSdk需要自己设置
            // compileSdk = libs.versions.compileSdkVersion.get().toInt()

            defaultConfig {
                // 这些也都要自己设置
//                    applicationId = "com.silencefly96.fundark"
//                    minSdk  = libs.versions.minSdkVersion.get().toInt()
//                    targetSdk  = libs.versions.targetSdkVersion.get().toInt()
//                    versionCode = libs.versions.versionCode.get().toInt()
//                    versionName = libs.versions.versionName.get()

                testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
            }

            buildTypes {
                release {
                    isMinifyEnabled = false
                    proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
                }
            }
            compileOptions {
                sourceCompatibility = JavaVersion.VERSION_1_8
                targetCompatibility = JavaVersion.VERSION_1_8
            }
            kotlinOptions {
                jvmTarget = "1.8"
            }
            buildFeatures {
                viewBinding = true
                dataBinding = true
            }
        }

        // 配置dependencies闭包
        with(target.dependencies) {}
    }

}

// 扩展函数,ApplicationExtension里面没有kotlinOptions,要自己写一个
fun CommonExtension<*, *, *, *>.kotlinOptions(block: KotlinJvmOptions.() -> Unit) {
    (this as ExtensionAware).extensions.configure("kotlinOptions", block)
}

差不多就是别人的基础上改了点,这里要注意下,application 和 library 模块不能通用,library 的要做点修改:

class LibraryOptimizePlugin : Plugin<Project> {
    override fun apply(target: Project) {
        with(target.plugins) {
            apply("com.android.library")
            ...
        }

        // 配置android闭包
        target.extensions.configure<LibraryExtension> {
            // ...
        }
    }
}

这里代码基本一样,就是换了 "com.android.library" 插件和 LibraryExtension,本来我想试试 LibraryExtension 和 ApplicationExtension 基类的 CommonExtension,但是好像不行。

注册插件及使用

上面插件写好后,就要去 build.gralle 里面注册插件了:

gradlePlugin {
    plugins.register("applicationOptimizePlugin") {
        id = "application-optimize-plugin"
        implementationClass = "com.silencefly96.plugins.ApplicationOptimizePlugin"
    }
    plugins.register("libraryOptimizePlugin") {
        id = "library-optimize-plugin"
        implementationClass = "com.silencefly96.plugins.LibraryOptimizePlugin"
    }
}

根据设置好的 id,我们就能在要使用的 module 里面引入,并删除被优化的内容,比如 app 里面:

plugins {
    id("application-optimize-plugin")
}

android {
    compileSdk = libs.versions.compileSdkVersion.get().toInt()
    defaultConfig {
        applicationId = "com.silencefly96.fundark"
        minSdk  = libs.versions.minSdkVersion.get().toInt()
        targetSdk  = libs.versions.targetSdkVersion.get().toInt()
        versionCode = libs.versions.versionCode.get().toInt()
        versionName = libs.versions.versionName.get()
    }
}

dependencies {
    //测试相关
    testImplementation(libs.junit)
    androidTestImplementation(libs.ext.junit)
    androidTestImplementation(libs.espresso.core)

    //从基础库继承各个依赖
    implementation(project(":module_base"))
}

因为引入了 Version Catalog,所以部分内容不能都优化掉,看需要吧,如果不用 Version Catalog 直接全优化掉也可以。

一些思考

搞到这我发现 Version Catalog 有问题啊,这不就是 buildSrc 一样么,每个要使用的模块都要手动去 implementation、api、kapt,没有使用 Composing build 插件那样一键搞定:

class VersionTestPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        println("VersionTestPlugin")
        // 添加afterEvaluate,不然无法添加依赖
        target.afterEvaluate {
            with(target.dependencies) {
                // 测试相关
                add("testImplementation", Libs.junit)
                add("androidTestImplementation", Libs.ext_junit)
                add("androidTestImplementation", Libs.espresso_core)
            }
        }
    }
}

后面想想好像使用 bundles 也是类似啊,而且使用插件的话,里面一键添加的依赖太多的话容易冗余,太少的话又没必要,想想好像 Version Catalog 这样子还更好一些。

反正 ext、buildSrc、Composing build 以及 Version Catalog 看需要使用吧。

参考文章

在学习的过程中参考了一些文章,还是得感谢各位大佬们的贡献:

【Gradle7.0】依赖统一管理的全新方式,了解一下~

迁移到 Gradle 7.x 使用 Version Catalogs 管理依赖

是时候弃用 buildSrc , 使用 Composing builds 加快编译速度了

Android Gradle 三方依赖管理

Android Gradle 三方依赖管理

总结

这里实践了下 Android 里面 Version Catalog 的使用,并且实现了 Version Catalog 打包成本地 maven 仓库插件,并在其他项目中依赖使用,感觉还挺有意思的!

声明:HEUE NOTE|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA 4.0协议进行授权

转载:转载请注明原文链接 - 使用Version Catalog管理Android依赖