Android依赖的统一管理

以NowInAndroid为范本

build-logic到底是干什么用的

NowInAndroid项目中的 build-logic 是一个专门存放"约定插件"的独立模块 ,它通过插件化的方式,集中管理和复用所有模块(如 appfeaturecore)的通用Gradle构建配置

它的主要目的是将项目中大量重复、分散的Gradle配置代码抽取出来,进行标准化和统一管理。具体来看:

  1. 消除重复配置 :各个功能模块的 build.gradle.kts 文件因此变得非常简洁,通常只需声明应用哪些约定插件和模块特定的依赖,其他通用配置(如Android插件、Compose、Java版本、测试设置等)都封装在插件中
  2. 作为"单一可信源" :当需要修改通用构建配置(例如,升级Kotlin版本或调整编译选项)时,只需在 build-logic 中修改一次,即可应用到所有相关模块,确保项目配置的一致性并极大降低维护成本

那的确很有必要,

看到一堆的业务模块,如果每个模块都要去重复配置和添加依赖,一旦要修改,又要全部修改一遍,实在是浪费时间。

与app同级的独立文件夹

并不隶属与app,独立的

约定优于配置

在实际的开发中,添加依赖时会想:每增加一个业务模块,就要配置一些相同的依赖,

那么,有没有办法,只需引入一句话就能搞定的呢?

有的,兄弟,包有的。

封装gradle自定义(约定)插件,

每一种插件负责一个特定的配置领域,(Android库,compose,测试等)

职责划分鲜明,组合使用,

避免创建全能的插件,配置组合不够精细,尾大不掉。

当然,

共用的可以约定,

特有的则是按照老办法使用依赖,

这不难理解,

版本目录/gradle/libs.versions.toml

通用的,可以不断收集扩张依赖库

即使是新项目也是拿来就用,

完全不必要,自己重新记录整理,因为这只是一个依赖目录,

具体的使用,还是要在功能模块的gradle中配置。

随着记录,目录会越来越长。

好在,从名称就能清楚知道,是什么依赖库,

如果不清楚的或者升级时需要同步升级的,那就需要备注好。

约定插件

在创建build-logic文件夹的时候,

这是一个不从属于app的独立gradle项目,

而且这实践是Google首次使用,

因此,创建的快捷方式并没有集成到AS中,

所以,文件夹都是手动创建的。(没有忘记怎么用文件夹创建项目)

  1. 项目目录创建完成

  2. 开始创建约定插件

  3. /convention/src/mian/kotlin

    创建一个名为AndroidApplicationConventionPlugin的kotlin类,

    约定Android相关的配置;

  4. /convention模块下

    创建build.gradle.kts

    在文件中,配置编译build-logic需要用到的插件和环境,

    然后是register注册约定插件,关联上kotlin类,

  5. /build-logic目录下

    创建setting.gradle.kts

    配置项目的根文件,

    将convention关联到build-logic项目下,

    现在可以了吗?

    还不行,

  6. 关联build-logic到主项目setting.gradle.kts

    在主项目的setting.gradle.kts文件里,

    pluginManagment的作用域里,

    添加includeBuild("build-logic"),

    然后,同步sync。

  7. 使用约定插件

    在业务模块build.gradle.kts文件里,

    plugins作用域中,alias关联上约定插件,

    试试有没有真的能够正常使用。

    以上就是创建约定插件的思路。

    还是能够看出来,只需要写一次,就可以到处使用,

    真的方便不少。

为什么apply()的参数不用libs.findLibrary

kotlin 复制代码
import com.mircles.allfuntionlearning.libs
import org.gradle.api.JavaVersion
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.dependencies


class AndroidLibraryConventionPlugin : Plugin<Project> {

    override fun apply(target: Project) {

        with(target) {
            // 1. 应用一些必需的基础插件
            pluginManager.apply {
                apply("com.android.library") // 应用Android库插件
                apply("org.jetbrains.kotlin.android") // 应用Kotlin Android插件
            }

            // 2. 配置Android闭包(这是约定的核心)
            extensions.configure<com.android.build.gradle.LibraryExtension> {
                compileSdk = 36 // 统一编译SDK版本

                defaultConfig {
                    minSdk = 24 // 统一最小SDK版本
                    // 其他通用配置...
                }

                compileOptions {
                    sourceCompatibility = JavaVersion.VERSION_17
                    targetCompatibility = JavaVersion.VERSION_17
                }

                // 如果项目使用Compose,可以在这里统一配置
                buildFeatures {
                    compose = true
                }
                //??配置的什么
                composeOptions {
                    kotlinCompilerExtensionVersion = "1.5.4"
                }
            }

            // 3. 添加通用依赖
            dependencies {
                // 添加对所有Android库模块都通用的依赖

                //引用的依赖指向libs.versions.toml文件,这是合理的。alias是?
                "implementation"(libs.findLibrary("androidx-core-ktx").get())
            }
        }

    }

}

能做到,但没必要。

这里体现的是Gradle插件依赖管理的"两级机制"。

apply()应用的参数是主项目的libs.versions的依赖id。

在编译的时候,build-logic会拉取对应的id和版本,编译到约定插件中。

这个问题非常关键,它触及了Gradle约定插件设计的核心模式。这两种不同的写法,恰恰体现了 "构建配置"与"项目依赖" 在管理哲学上的根本区别。

简单来说:pluginManager.apply 是在"武装"这个约定插件自身,而 dependencies.add 是在为最终应用该插件的业务模块"预装"通用依赖。

用安装操作系统的比喻来理解:

  • 插件 (pluginManager.apply):就像操作系统安装盘自带的驱动程序 。没有它们,安装盘本身就无法识别基础硬件(如Android、Kotlin环境)。这些驱动必须在制作安装盘时就打包进去
  • 库依赖 (dependencies.add):就像操作系统默认安装的软件包(如记事本、浏览器)。这些软件包的定义和版本信息保存在一个"软件仓库列表"里,安装系统时再从列表里实时下载安装。

下面是这两个层级的详细说明:

第一级:构建"安装盘"(编译 build-logic 项目)

当Gradle编译你的 build-logic 项目,生成约定插件(.class文件)时:

  1. pluginManager.apply("com.android.library")

    • 时机 :在编译build-logic过程中执行。
    • 作用 :告诉Gradle:"在编译我(这个约定插件)的时候,请把Android库插件和Kotlin插件的能力链接进来。" 只有这样,接下来的 android {}kotlinOptions {} 等扩展才能在代码中被识别和调用。
    • 结果 :这些插件被编译到约定插件的字节码里,成为其固有的一部分
  2. libs.findLibrary("kotlin.stdlib").get()

    • 时机 :这行代码本身 在编译build-logic时被编译。但 findLibrary 这个动作并不会发生,它只是一个"查找指令"被保存下来。
    • 关键libs 对象此时只被用作类型安全的引用build-logic 项目需要声明对 version-catalog API 的依赖,才能认识 libs 这个类型,但并不需要(也不会)在此时解析出具体的 kotlin-stdlib:1.9.0。具体的查找动作被推迟到第二级。

第二级:使用"安装盘"(业务模块应用约定插件)

当你在 :feature:settings 模块中执行 plugins { id("nowinandroid.android.library") } 时:

  1. Gradle会加载并执行已编译好的 AndroidLibraryConventionPluginapply 方法。

  2. 当执行到 dependencies.add("implementation", libs.findLibrary("kotlin.stdlib").get()) 时:

    • 核心 :这里的 libs 对象,不再是 build-logic 项目里的那个,而是当前业务模块:feature:settings)的 libs 对象!
    • 查找 :它会在当前模块的上下文中,去查找 libs.versions.toml 中定义的 kotlin.stdlib,解析出完整的依赖坐标。
    • 添加 :将这个解析好的依赖,以 implementation 配置添加到当前模块中。

为什么依赖必须用 findLibrary 这种"推迟"写法?

这是为了获得最大的灵活性与可维护性

  1. 单一真实来源 :所有模块的依赖版本始终由根项目的 libs.versions.toml 控制。如果你想升级Kotlin版本,只需在toml文件中改一处,所有应用了约定插件的模块都会自动使用新版本。
  2. 避免耦合 :如果像插件一样在 build-logic 中固化依赖版本,那么 build-logic 就需要为每个库声明具体版本,这会使它变得臃肿,且与业务模块的依赖树紧密耦合。
  3. 类型安全与IDE支持libs.findLibrary 提供了类型安全和IDE自动补全(因为 libs 对象是从Catalog生成的API)。这比写字符串 "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.0" 要安全、方便得多。

总结与对比

特性 pluginManager.apply("插件ID") dependencies.add(libs.findLibrary("xxx"))
作用对象 装备约定插件本身,使其具有特定能力(配置Android、Kotlin) 应用此插件的业务模块添加通用库依赖
解析时机 编译 build-logic 项目时解析并锁定 业务模块应用插件时,从该模块的Catalog中解析
版本管理 版本在 build-logic/build.gradle.ktsdependencies 中锁定 版本延迟到业务模块中,从 libs.versions.toml 解析
类比 安装盘自带的驱动(打包时固化) 从软件库安装的默认软件(安装时根据列表下载)

所以,这不是不一致,而是一种精妙的分层设计。它确保了约定插件本身的稳定性和业务模块依赖管理的灵活性。

如果你尝试在 build-logic 的插件代码中硬编码一个库的字符串坐标,你会发现业务模块确实能用到这个依赖,但版本升级将变得极其困难 ,因为你必须重新发布 build-logic 插件。而现在的"推迟查找"模式完美地避免了这个问题。

相关推荐
国家二级编程爱好者2 小时前
Android Lottie使用,如何自定义LottieView?
android·前端
南囝coding2 小时前
《独立开发者精选工具》第 025 期
前端·后端
@淡 定2 小时前
Dubbo + Nacos 完整示例项目
前端·chrome·dubbo
毕设源码-邱学长2 小时前
【开题答辩全过程】以 基于web的博客论坛系统的设计与实现为例,包含答辩的问题和答案
前端
就叫曲奇饼干吧2 小时前
前端面试题整理(方便自己看的)
前端·面试
拖拉斯旋风2 小时前
防抖(Debounce)实战解析:如何用闭包优化频繁 AJAX 请求,提升用户体验
前端
老前端的功夫2 小时前
TypeScript 全局类型声明:declare关键字的深度解析与实战
linux·前端·javascript·ubuntu·typescript·前端框架
golang学习记2 小时前
VS Code 1.107 发布:AI 不再是插件,而是编辑器的「第一大脑」
前端
EndingCoder3 小时前
TypeScript 入门:理解其本质与价值
前端·javascript·ubuntu·typescript·node.js