随着移动应用功能的日益复杂和多样化,组件化架构已经成为管理应用代码的一种重要方式。组件化架构的发展使得应用能够以模块化的方式组织和管理,提供更高的灵活性和可扩展性。然而,随之而来的挑战是组件的依赖和版本的管理。在组件化架构下,组件之间存在复杂的依赖关系,而版本管理则需要确保不同组件的兼容性和一致性。本文将探讨组件版本管理的重要性,并介绍一些解决方案和最佳实践,以帮助大家有效地管理组件的版本,确保应用的稳定性和可维护性。
前言
VeSync是一个IoT设备管理的App,随着时间的推移,它不断演变为一个功能复杂的应用程序。除了IoT设备管理功能外,它还包括wellness、商城、社区等其他功能。为了应对这种功能复杂性的增加,VeSync的架构也从最初的单一主工程App逐渐发展为多层级组件化架构。
在架构演变的过程中,组件依赖依然采用 ext 和直接按库引用的依赖方式,在实际开发过程中,引出了下面的问题:
- 代码重复、维护困难、灵活性受限、无法统一依赖(每个组件都直接按库引入)。
- 三方库版本更新无提示,无法追踪 (使用的ext文件管理)。
- 三方库及内部库声明耦合,更新依赖库需要人工审核,实际内部库更新是不需要审核的,这里就产生多余的审核成本。
- 没有限制开发者依赖方式,开发者可以自己在项目直接按库引入。
接下来,我们将逐一介绍Android 项目中常见的依赖管理方案,包括直接按库引入、ext 管理、buildSrc、Composing build 以及 Version Catalogs,同时也会介绍这些方案相应的优势、劣势以及适用场景,看看如何解决上面提到的问题,并帮助开发者更好地理解和选择适合自己项目的依赖管理方案。
一、Android 依赖管理方案详解
1.1 直接按库引入
直接按库引入是一种常见的依赖管理方式,通过在 build.gradle 文件中直接声明依赖库的方式来引入所需的库。
module 1
arduino
// 直接使用
dependencies {
implementation "androidx.appcompat:appcompat:1.3.1"
}
module 2
arduino
// 直接使用
dependencies {
implementation "androidx.appcompat:appcompat:1.3.1"
}
相同的代码重复的写在 gradle 中。一旦appcompat有更新,需要手动修改所有的module,如果忘记修改某个module,这样一个项目里就有多个版本的appcompat。为了解决统一依赖的问题,ext管理出现了。
1.2 ext 管理
ext 管理是一种通过在 build.gradle 文件中定义 ext 变量来管理依赖版本的方式。
ini
//config.gradle
ext {
kotlin_version = '1.8.0'
android = [
compileSdkVersion : 34,
minSdkVersion : 26,
targetSdkVersion : 34,
versionCode : 1,
versionName : "1.0",
]
dependencies = [
// 基本库
kotlin: "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version",
core_ktx: 'androidx.core:core-ktx:1.3.2',
appcompat: 'androidx.appcompat:appcompat:1.3.1',
material: 'com.google.android.material:material:1.4.0',
constraintlayout: 'androidx.constraintlayout:constraintlayout:2.0.1',
]
}
实际使用:
module 1
csharp
//build.gradle
apply from: 'config.gradle'
...
dependencies {
implementation rootProject.ext.dependencies.kotlin
}
module 2
csharp
//build.gradle
apply from: 'config.gradle'
...
dependencies {
implementation rootProject.ext.dependencies.kotlin
}
这种把依赖和版本统一封装在一个文件中,解决了依赖版本统一的问题,但是依赖是文件的形式,是没有更新提示及代码提示的,也不适用于kts脚本。buildSrc出现了。
1.3 buildSrc
gradle官方文档 给出的解释
当运行 Gradle 时会检查项目中是否存在一个名为 buildSrc 的目录。然后 Gradle 会自动编译并测试这段代码,并将其放入构建脚本的类路径中, 对于多项目构建,只能有一个 buildSrc 目录,该目录必须位于根项目目录中, buildSrc 是 Gradle 项目根目录下的一个目录,它可以包含我们的构建逻辑,与脚本插件相比,buildSrc 应该是首选,因为它更易于维护、重构和测试代码。
- 在项目的根目录下创建一个名为"buildSrc"的文件夹,名字不要乱改,gradle会特别识别它。
- 创建以下文件
build.gradle.kts
scss
plugins {
`kotlin-dsl`
}
repositories{
mavenCentral()
google()
gradlePluginPortal()
}
Dependencies.kt
ini
// Dependencies.kt
// 不要package!
object BuildVersion {
const val compileSdkVersion = 34
const val minSdkVersion = 26
const val targetSdkVersion = 34
const val versionCode = 1
const val versionName = "1.0"
}
object Versions {
//基本库
const val kotlin = "1.4.21"
const val core_ktx = "1.3.2"
const val appcompat = "1.2.0"
const val material = "1.2.1"
const val constraintlayout = "2.0.1"
}
object VersionConfig {
const val APPCOMPAT= "androidx.appcompat:appcompat:${Versions.appcompat}"
}
实际使用:
module 1
scss
// build.gradle.kts
...
dependencies {
implementation(VersionConfig.kotlin)
}
module 2
scss
// build.gradle.kts
...
dependencies {
implementation(VersionConfig.kotlin)
}
buildSrc解决了ext没有代码提示的问题,但是buildSrc只能用在单个项目中,而且修改了buildSrc中的一个依赖,重新编译是整个项目都需要编译的,这样就影响了编译速度。
1.4 Composing builds
Composing builds 是一种将依赖配置文件拆分成多个独立的 build.gradle 文件的方式。
复合构建只是包含其他构建的构建. 在许多方面,复合构建类似于 Gradle 多项目构建,不同之处在于,它包括完整的 builds ,而不是包含单个 projects
- 组合通常独立开发的构建,例如,在应用程序使用的库中尝试错误修复时
- 将大型的多项目构建分解为更小,更孤立的块,可以根据需要独立或一起工作
新建一个项目,或者module ,开发一个插件,
kotlin
// build.gradle.kts
plugins {
id("java-gradle-plugin")
id("org.jetbrains.kotlin.jvm") version "1.8.10"
}
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
dependencies {
//添加Gradle相关的API,否则无法自定义Plugin和Task
implementation(gradleApi())
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.10")
}
gradlePlugin {
plugins {
create("version") {
//添加插件
id = "com.vesync.plugin.version"
//在根目录创建类 VersionPlugin 继承 Plugin<Project>
implementationClass = "com.vesync.plugin.version.VersionPlugin"
}
}
}
// VersionPlugin.kt
class VersionPlugin : Plugin<Project> {
override fun apply(target: Project) {
}
}
// Dependencies.kt
object BuildVersion {
const val compileSdkVersion = 34
const val minSdkVersion = 26
const val targetSdkVersion = 34
const val versionCode = 1
const val versionName = "1.0"
}
object Versions {
//基本库
const val kotlin = "1.4.21"
const val core_ktx = "1.3.2"
const val appcompat = "1.2.0"
const val material = "1.2.1"
const val constraintlayout = "2.0.1"
}
object VersionConfig{
const val kotlin = "org.jetbrains.kotlin:kotlin-stdlib:${Versions.kotlin}"
}
实际使用:
scss
//项目 build.gradle.kts 使用
dependencies {
implementation(VersionConfig.kotlin)
}
Composing builds继承了buildSrc所有的优点。当修改了一个依赖版本号,ComposingBuild项目Build的时间几乎比 buildSrc项目快6倍。
1.5 Version Catalogs
Version Catalogs 是一种通过创建版本目录文件来管理依赖版本的方式。
1、启用 version Catalogs
gradle 7.4.1之前:
scss
// gradle 7.4.1之前并不是稳定版本功能,所以需要预先开启功能预览
enableFeaturePreview("VERSION_CATALOGS")
gradle 7.4.1 开始是正式版,默认启用 不需要添加
2、默认方式 gradle目录下新加libs.versions.toml
TOML 文件由4个主要部分组成
versions\] 用于声明可以被依赖项引用的版本
\[libraries\] 用于声明依赖的别名
\[bundles\] 用于声明依赖包(依赖组)
\[plugins\] 用于声明插
```ini
// toml 文件
[versions]
agp = '8.1.4'
kotlin-version = '1.8.21'
[libraries]
classpath-tool-build = { group = "com.android.tools.build", name = "gradle", version.ref = "agp" }
classpath-maven-gradle-plugin = { group = "com.github.dcendents", name = "android-maven-gradle-plugin", version = "2.1" }
classpath-kotlin-gradle-plugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin-version" }
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
javaLibrary = { id = "java-library" }
```
实际使用:
```scss
// build.gradle.kts 使用
alias(libs.plugins.androidApplication)
implementation(libs.lib.kotlin)
// setting.gradle.kts 自定义toml文件存放位置
dependencyResolutionManagement {
......
// 第二种方式使用版本目录
libs {
from(files("./libs.versions.toml"))
}
}
```
借助 [Gradle 版本目录](https://link.juejin.cn?target=https%3A%2F%2Fdocs.gradle.org%2Fcurrent%2Fuserguide%2Fplatforms.html "https://docs.gradle.org/current/userguide/platforms.html"),您能够以可扩容的方式添加和维护依赖项和插件。使用 Gradle 版本目录,您可以在拥有[多个模块](https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.android.com%2Ftopic%2Fmodularization%3Fhl%3Dzh-cn "https://developer.android.com/topic/modularization?hl=zh-cn")时更轻松地管理依赖项和插件。您不必对各个 build 文件中的依赖项名称和版本进行硬编码,也不必在每次需要升级依赖项时都更新每个条目,而是可以创建一个包含依赖项的中央版本目录,各种模块可在 Android Studio 协助下以类型安全的方式引用该目录
### **1.6 方案对比**
经过上面的方案介绍,我们从如下几个方面对比一下各个方案的差异。
| | **直接引入** | **ext管理** | **buildSrc** | **Composing build** | **Version Catalogs** |
|--------|-----------|-----------|--------------|---------------------|----------------------|
| 统一依赖 | 否 | 是 | 是 | 是 | 是 |
| 代码提示 | 否 | 否 | 是 | 是 | 是 |
| 更新提示 | 是 | 否 | 否 | 否 | 是 |
| 修改依赖编译 | 增量 | 增量 | 全量 | 增量 | 增量 |
| 依赖组 | 否 | 否 | 否 | 否 | 是 |
| 语法 | groovykts | groovykts | javakotlin | javakotlin | toml |
| 学习成本 | 低 | 低 | 较低 | 高 | 较高 |
| 建议项目类型 | 小型 | 中小型 | 中大型 | 大型模块化组件化 | 大型模块化组件化 |
## **二、VeSync的组件化依赖管理实践**
### 2.1 **方案选择**
为了解决我们遇到的问题,结合上面的组件依赖和版本管理方案的调研,最终我们选择使用Version Catalogs方案进行内部库依赖管理,Composing build管理三方库依赖,引入该依赖管理方案后,App架构调整为下图的方式:

选择Version Catalogs及Composing build后,版本管理当中遇到的问题得到了如下的改善:
1)通过配置远程toml文件,解决统一依赖代码重复等问题。
2)Version Catalogs是Google推荐使用依赖方案,具有代码提示、更新提示等优点。
3)Composing build管理三方库依赖,使内部库和三方库的依赖管理分开,更好的审核三方库的引入。
4)针对开发者强制校验开发者按规范引入依赖。
### **2.2 迁移实现**
① 内部库依赖开一个仓库,里面放一个libs.version.toml文件,分支对应项目分支,组件及项目引用改文件。开发一个插件在编译前把toml文件下载到本地,实现一个App 多个组件的依赖统一。
· 之前使用的ext 方案修改为Version Catalogs(通过脚本把各个App ext文件批量转换成toml文件)
```ini
// toml 文件
[versions]
agp = '8.1.4'
kotlin-version = '1.8.21'
...
[libraries]
classpath-tool-build = { group = "com.android.tools.build", name = "gradle", version.ref = "agp" }
classpath-maven-gradle-plugin = { group = "com.github.dcendents", name = "android-maven-gradle-plugin", version = "2.1" }
classpath-kotlin-gradle-plugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin-version" }
...
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
javaLibrary = { id = "java-library" }
...
```
内部库管理流程:

使用:
```csharp
// setting.gradle.kts
val tomlVersionUrl = "https://xxxxx.libs.versions.toml"
....下载到gradle目录下
// 也可以下载到指定位置,自定义toml文件存放位置
dependencyResolutionManagement {
......
// 第二种方式使用版本目录
libs {
from(files("./libs.versions.toml"))
}
}
// build.gradle.kts 使用
dependencies {
implementation(libs.vesync.logger)
}
```
② 三方库依赖开一个项目,开发一个Composing build插件,分支对应项目分支,更新后修改libs.version.toml中的version依赖
· 把ext文件找出三方库,通过脚本转成Composing build文件
```kotlin
object BuildVersion {
const val MIN_SDK_VERSION = 24
const val TARGET_SDK_VERSION = 33
const val COMPILE_SDK_VERSION = 33
...
}
object VersionConfig {
...
const val APPCOMPAT = "androidx.appcompat:appcompat:1.6.1"
...
}
class VersionPlugin : Plugin