SimpMusic 的 Android 构建逻辑:用 Convention Plugins 与 Version Catalog 打造可扩展的 Gradle 构建体系
音视频【免费下载链接】SimpMusicA cross-platform music app using YouTube Music for backend项目地址https://gitcode.com/gh_mirrors/si/SimpMusic点击查看免费下载导读本文围绕 SimpMusic 仓库中的构建配置实践系统讲解如何用Gradle Convention Plugins约定插件与Version Catalog版本目录搭建一套可扩展、可维护的 Android 多模块构建体系。你将掌握build-logic目录结构、settings.gradle.kts复合构建接入、libs.versions.toml的版本统一管理以及自定义插件的创建与注册并结合 SimpMusic 仓库现状理解从逐模块复制粘贴演进到插件复用的完整路径。一、为什么要收敛构建逻辑告别 copy-pasteSimpMusic 是一个典型的多模块项目仅在本仓库根目录下就同时管理着androidApp、vivoAndroidApp、composeApp、desktopApp以及cast、cast-empty、crashlytics、crashlytics-empty、lastfm、lastfm-empty等一批功能模块。这种结构下如果每个模块都独立写一份build.gradle.kts会出现大量重复配置Compose 编译选项compose true、Compose Compiler 插件Kotlin 编译选项jvmTarget、freeCompilerArgs各模块都要重复声明的 SDK 版本、minSdk、compileSdkHilt/Koin 等依赖注入配置。从源码结构看SimpMusic 中androidApp/build.gradle.kts与vivoAndroidApp/build.gradle.kts就存在明显的配置同构——后者在文件头注释中甚至直接写明Everything except the package and the ABI mirrors androidApp/build.gradle.kts: keep the two in step除包名与 ABI 外完全镜像 androidApp两者需保持同步。这正是文档所述痛点Stop copy-pasting code betweenbuild.gradle.ktsfiles在真实项目中的直接体现。解决方案的核心思路把 Compose 设置、Kotlin 选项、依赖注入等公共构建逻辑从各个模块的脚本中抽离出来集中到可复用的插件Convention Plugins中配合 Version Catalog 统一管理版本号。二、目标项目结构build-logic 复合构建按文档方案先确保项目具备一个build-logic目录并在settings.gradle.kts中将其作为复合构建composite build引入root/ ├── build-logic/ │ ├── convention/ │ │ ├── src/main/kotlin/ │ │ │ └── AndroidApplicationConventionPlugin.kt │ │ └── build.gradle.kts │ ├── build.gradle.kts │ └── settings.gradle.kts ├── gradle/ │ └── libs.versions.toml ├── app/ │ └── build.gradle.kts └── settings.gradle.kts其中build-logic/settings.gradle.kts是复合构建自身的设置文件负责声明其内部子项目如convention与所用插件仓库build-logic/convention是一个独立的 Gradle 子项目src/main/kotlin/下存放各约定插件的 Kotlin 源码build.gradle.kts负责注册这些插件根目录的gradle/libs.versions.toml是 Version Catalog供根构建与 build-logic 共享各功能模块如app通过alias(...)引用约定插件脚本体积大幅缩减。三、Step 1在 settings.gradle.kts 中接入复合构建includeBuild(build-logic)会把build-logic作为一个独立的复合构建引入其提供的插件对所有子项目可见。同时配置插件仓库与依赖解析仓库// settings.gradle.kts pluginManagement { includeBuild(build-logic) repositories { google() mavenCentral() gradlePluginPortal() } } dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() } }几点关键语义pluginManagement决定插件的解析来源与顺序includeBuild(build-logic)让 Gradle 优先从复合构建中寻找插件 ID未命中的再回落至下方仓库RepositoriesMode.FAIL_ON_PROJECT_REPOS强制所有模块统一走根级仓库声明禁止各模块自行声明仓库避免多模块之间因仓库不一致导致的解析问题实际项目中可按需追加仓库。例如 SimpMusic 的根 settings.gradle.kts 就额外加入了 JitPackmaven { setUrl(https://jitpack.io) }与 JogAmp 仓库并在注释中记录了移除已关停的 oss.sonatype.org 的经验——仓库列表应当根据实际依赖随时收敛。与 SimpMusic 现状的对照SimpMusic 根 settings.gradle.kts 已经完整应用了本文档的插件仓库 依赖仓库管理骨架pluginManagement中声明了google()、mavenCentral()、gradlePluginPortal()及自定义仓库dependencyResolutionManagement同样设置了FAIL_ON_PROJECT_REPOS。它尚未引入的是includeBuild(build-logic)一行——也就是说SimpMusic 目前只完成了 Version Catalog 化build-logic复合构建属于可选的下一步演进。此外SimpMusic 还开启了enableFeaturePreview(TYPESAFE_PROJECT_ACCESSORS)让模块间依赖可以写成projects.composeApp、projects.data这种类型安全访问器见 androidApp/build.gradle.kts 中implementation(projects.composeApp)的用法。四、Step 2在 libs.versions.toml 中统一定义依赖与插件Version Catalog 的核心价值是让库与插件的版本各有一个唯一出处。典型写法如下库部分以注释省略[versions] androidGradlePlugin 8.2.0 kotlin 1.9.20 [libraries] # ... [plugins] android-application { id com.android.application, version.ref androidGradlePlugin } android-library { id com.android.library, version.ref androidGradlePlugin } kotlin-android { id org.jetbrains.kotlin.android, version.ref kotlin } # Define your own plugins here nowinandroid-android-application { id nowinandroid.android.application, version unspecified }关键点[versions]集中存放版本号libraries与plugins通过version.ref引用一处升级全局生效自己写的约定插件在 Version Catalog 中声明为version unspecified——因为它来自复合构建而非外部仓库没有独立版本号声明后Gradle 会为每个条目生成类型安全的访问器例如libs.plugins.android.application、libs.versions.kotlin。SimpMusic 的实际 Version CatalogSimpMusic 的 gradle/libs.versions.toml 是一个规模可观的真实样例体现了文档所述模式在生产项目中的完整形态三段式组织[versions]超过 90 个版本号如android 9.4.1、kotlin 2.4.20、compose-bom 2026.09.00[libraries]约 130 条依赖声明[plugins]约 20 个插件条目版本号可被构建脚本反向读取androidApp/build.gradle.kts中versionCode libs.versions.version.code.get().toInt()、versionName libs.versions.version.name.get()让 APK 版本号与 toml 中的version-name/version-code单一来源保持一致desktopApp/build.gradle.kts 也用它来给桌面端设version插件与库共同管理[plugins]中除了android-application、android-library还包含compose-compiler、kotlin-serialization、ksp、room、sentry-gradle等全部通过version.ref与版本段联动带注释的版本选择记录toml 中大量注释记录了版本间兼容性约束如 material3 与 material3-multiplatform 必须同步升级这本身就是 Version Catalog 化给团队带来的可维护性红利。五、Step 3创建并注册 Convention Plugin约定插件的本质是一段把公共配置应用到目标项目的逻辑。在build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt中定义import com.android.build.api.dsl.ApplicationExtension import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.configure class AndroidApplicationConventionPlugin : PluginProject { override fun apply(target: Project) { with(target) { with(pluginManager) { apply(com.android.application) apply(org.jetbrains.kotlin.android) } extensions.configureApplicationExtension { defaultConfig.targetSdk 34 // Configure common options here } } } }要点说明PluginProject的apply(target: Project)是插件入口with(target)让后续pluginManager、extensions都在目标项目上生效通过pluginManager.apply(...)把官方插件内聚进约定插件目标模块就不必再重复声明这些官方插件extensions.configureApplicationExtension { ... }是 AGP 推荐的类型安全配置方式可在此统一compileSdk、minSdk、targetSdk、buildFeatures等公共项。随后必须在build-logic/convention/build.gradle.kts中注册该插件才能让插件 ID 对外可见gradlePlugin { plugins { register(androidApplication) { id nowinandroid.android.application implementationClass AndroidApplicationConventionPlugin } } }gradlePlugin是 Gradle 的 Java/Kotlin DSL 插件机制id是暴露给其他模块的插件标识implementationClass指向插件的具体实现类。约定插件的公共入口应进一步扩展到公共配置例如android-application、android-library、kotlin-android、Compose 与依赖注入相关插件从而覆盖项目里所有模块的公共配置。六、Usage在模块中应用约定插件在功能模块如app/build.gradle.kts中只需要一行即可替代原本几十行的公共配置plugins { alias(libs.plugins.nowinandroid.android.application) }alias(libs.plugins.xxx)会解析 Version Catalog 中声明的插件条目由于约定插件内部已经完成了官方插件的 apply 与公共 DSL 配置模块级脚本得以大幅瘦身只需保留模块特有内容如namespace、专属依赖。这正是文档This drastically cleans up module-level build files所述的效果。SimpMusic 当前采用的声明模式SimpMusic 尚未落地约定插件但已经充分使用了根项目统一声明 模块 alias 引用的过渡形态根 build.gradle.kts 中以alias(libs.plugins.android.application) apply false等方式统一声明所有插件apply false表示只将插件加入构建的类路径、不立即应用避免插件版本在多处重复各模块只做一次 alias例如 androidApp/build.gradle.kts 的plugins { alias(libs.plugins.android.application); alias(libs.plugins.sentry.gradle); alias(libs.plugins.compose.compiler) }cast/build.gradle.kts 的alias(libs.plugins.android.library)与alias(libs.plugins.compose.compiler)根脚本同时用subprojects { ... }做全局 KMP 编译参数与依赖解析策略的收敛见 build.gradle.kts例如通过resolutionStrategy.force(...)强制统一 nanojson 版本以规避NoSuchMethodError。从源码结构看SimpMusic 的androidApp与vivoAndroidApp之间明显的配置同构同样一组插件、同样的compileSdk 37、同样的minSdk 26、同样格式的packaging排除规则正是文档方案所要消除的重复——这也是将上述公共配置抽取为AndroidApplicationConventionPlugin的最佳候选场景。七、实战迁移路径从 SimpMusic 现状到 Convention Plugins结合仓库实际情况可以从Version Catalog 化继续推进到约定插件化建议按以下步骤落地建立build-logic目录创建build-logic/settings.gradle.kts、build-logic/build.gradle.kts并引入kotlin-dsl插件以支持用 Kotlin 编写插件源码接入根构建在根 settings.gradle.kts 的pluginManagement中加入includeBuild(build-logic)编写第一个约定插件把androidApp与vivoAndroidApp共有的配置compileSdk、minSdk、targetSdk、versionCode/versionName读取、buildFeatures、packaging排除规则抽入AndroidApplicationConventionPlugin在 Version Catalog 中登记参照version unspecified的写法在 gradle/libs.versions.toml 的[plugins]段为每个约定插件声明条目逐模块替换将模块脚本中的公共插件列表替换为一行alias(libs.plugins.自定义插件)仅保留模块特有配置并用./gradlew :androidApp:assembleDebug逐模块验证构建按需拆分当模块类型分化明显时如 Android 库、KMP 模块、桌面 JVM 模块按类型拆分为多个约定插件AndroidLibraryConventionPlugin、KmpLibraryConventionPlugin等保持每个插件职责单一。需要注意的前提与限制约定插件修改后会作用于所有应用它的模块属于一次配置、全局生效引入前应先在 CI 上充分验证build-logic本身是独立的构建它的仓库解析需要在其自己的settings.gradle.kts中声明不能依赖根构建的仓库SimpMusic 已在根 gradle.properties 中开启org.gradle.unsafe.configuration-cachetrue编写约定插件时应注意避免在doLast中捕获Project/layout等对象引用以免破坏配置缓存兼容性这一点与 desktopApp/build.gradle.kts 中对个别任务标注notCompatibleWithConfigurationCache的处理思路一致。八、最佳实践小结版本单一来源所有库与插件的版本只存在于libs.versions.toml脚本中通过类型安全的libs.*访问器引用仓库收敛坚持FAIL_ON_PROJECT_REPOS根级统一管理仓库列表随依赖变化增删参考 SimpMusic 移除已停服 OSSRH 的做法公共配置进插件凡是两个以上模块重复的配置优先抽取为约定插件而不是继续复制粘贴渐进式演进即使暂时不引入build-logic先完成根项目apply false声明 模块alias引用 Version Catalog这套组合也能消除绝大部分版本漂移问题SimpMusic 当前正是这一中间形态可作为后续向 Convention Plugins 迁移的现成样板。参考文件导航关联文档本文骨架来源.claude/skills/android-gradle-logic/SKILL.md根构建配置settings.gradle.kts、build.gradle.kts、gradle.propertiesVersion Catalog 实例gradle/libs.versions.toml模块脚本对照androidApp/build.gradle.kts、vivoAndroidApp/build.gradle.kts、cast/build.gradle.kts、lastfm/build.gradle.kts、desktopApp/build.gradle.kts打包脚本build_and_sign_apk.sh赞分享音视频【免费下载链接】SimpMusicA cross-platform music app using YouTube Music for backend项目地址https://gitcode.com/gh_mirrors/si/SimpMusic点击查看免费下载相关推荐Thunderbird for Android 构建插件体系基于 Gradle Convention Plugins 的统一构建逻辑管理实践Thunderbird for Android 构建插件体系基于 Gradle Convention Plugins 的统一构建逻辑管理实践 导读 Thund移动开发企业应用Robolectric 构建逻辑build-logic解析Convention Plugins 与 Error Prone 聚合报告机制Robolectric 构建逻辑build logic解析Convention Plugins 与 Error Prone 聚合报告机制 build lo测试移动开发AnkiDroid 构建体系剖析buildSrc 约定插件与 Gradle 构建逻辑复用实战AnkiDroid 构建体系剖析buildSrc 约定插件与 Gradle 构建逻辑复用实战 导读 本文以 buildSrc/README.md https:移动开发教育上一篇Arklex framework完全指南构建智能AI Agent的终极工具包下一篇Win11Debloat完整指南给Windows 11做一次系统深度优化创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

从慢查询到Elasticsearch:倒排索引与分布式搜索入门避坑

从慢查询到Elasticsearch:倒排索引与分布式搜索入门避坑

第一次认真接触 Elasticsearch,不是赶新技术,而是被一条慢查询逼的。当时我在维护一个订单检索模块,数据量涨到六百多万条以后,按商品名称做模糊搜索的接口开始频繁超时。改 SQL、加索引、换缓存,折腾了一圈&#xff0…

2026/10/14 2:22:24 阅读更多 →
LVS+Keepalived+Nginx Docker负载均衡集群实战

LVS+Keepalived+Nginx Docker负载均衡集群实战

1. 项目概述:为什么一个负载均衡集群要绕这么大一圈?“基于Docker搭建lvskeepalivednginx实现高性能负载均衡集群”——这个标题里藏着三层技术栈的叠加,不是炫技,而是现实业务中反复踩坑后沉淀下来的务实选择。我最早在某高校实验…

2026/10/14 2:22:24 阅读更多 →
风电水电联合调度优化算法与工程实践

风电水电联合调度优化算法与工程实践

1. 项目背景与核心价值去年参与某省级电网清洁能源消纳项目时,我第一次接触到风-水联合调度这个课题。当时面对风电出力的强随机性和水电调节的滞后性,团队在制定优化运行方案时遇到了不少挑战。这个经历让我深刻认识到:在双碳目标下&#xf…

2026/10/14 2:21:24 阅读更多 →

最新新闻

容器内nvidia-smi报Unknown Error的排查与根治

容器内nvidia-smi报Unknown Error的排查与根治

凌晨两点半,工作群里弹出一条消息:某个长期跑的容器,前一秒还好好的,后一秒容器里的nvidia-smi直接报Failed to initialize NVML: Unknown Error。宿主机上执行nvidia-smi一切正常,新建一个临时容器也能正常看到卡&…

2026/10/14 3:03:31 阅读更多 →
年会抽奖九宫格+转盘小程序:动画与随机逻辑避坑指南

年会抽奖九宫格+转盘小程序:动画与随机逻辑避坑指南

简介:一份面向企业年会场景的抽奖小程序资源,包含九宫格抽取幸运者和转盘抽奖两个页面,无需数据库,所有数据以静态方式配置,可直接用于年会“抽人发红包”环节。规则设计完整:先用九宫格抽出一名免红包幸运…

2026/10/14 3:03:31 阅读更多 →
DeepEMD:用可微分推土机距离破解小样本图像分类难题

DeepEMD:用可微分推土机距离破解小样本图像分类难题

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 3:03:31 阅读更多 →
FFT傅立叶变换股价周期分析:从频谱原理到量化回测实战

FFT傅立叶变换股价周期分析:从频谱原理到量化回测实战

先说一句大家可能不太爱听的话:把傅立叶变换用在股价预测上,市场上九成的人都觉得这是“花架子”,但真正用好了的人,都把它当成一种周期量化的辅助工具,而不是一台印钞机。我自己在量化分析这条路上摸索了几年&#xf…

2026/10/14 3:03:31 阅读更多 →
CLRC663 Pin-to-Pin替代实战:硬件迁移与固件适配全解析

CLRC663 Pin-to-Pin替代实战:硬件迁移与固件适配全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 3:03:31 阅读更多 →
Apache Mesos 0.18.0 发布:隔离策略重构为 Containerizer / Isolator API 的技术解读

Apache Mesos 0.18.0 发布:隔离策略重构为 Containerizer / Isolator API 的技术解读

云原生 【免费下载链接】mesos Apache Mesos 项目地址: https://gitcode.com/gh_mirrors/mesos2/mesos 点击查看 免费下载 Apache Mesos 0.18.0 是一次以架构演进为核心的里程碑版本:它将此前"单一隔离策略"的实现重构为 Containerizer API&a…

2026/10/14 3:02:31 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →