资讯动态

Android aar 发布到 Artifactory 并实现依赖传递的完整指南

发布时间:2026/10/14 9:37:25 来源:尧图企业网站定制
1. 项目背景与核心需求拆解1.1 这个标题到底在说什么把 Android 项目里的某个模块打包成 aar推到 Artifactory 私服并且让这个 aar 在被别人依赖时能自动把它自己依赖的其他库也带出来——这就是标题的全部含义。听起来像一句话的事但真正动过手的人都知道这里面藏着好几个容易翻车的点。我在实际项目里做过很多次这种操作踩过的坑包括但不限于aar 推上去了但别人拉下来跑不起来、依赖传递丢失导致对方要手动补一堆 implementation、pom 文件生成不完整、多模块嵌套时依赖关系断裂。这些问题在本地测试时往往发现不了等到别的团队接入时才暴露排查起来非常费时间。这篇文章适合三类人看第一类是第一次接触 aar 发布流程的 Android 开发者想搞清楚从模块配置到推送私服的完整链路第二类是已经会发 aar 但被依赖传递问题困扰的人想知道怎么让 aar 带上自己的依赖树第三类是负责搭建团队内部组件仓库的工程师需要一套可复用的发布方案。1.2 为什么不用简单的 maven-publish 就够了很多人第一反应是用maven-publish插件配合publishToMavenLocal或者直接推到某个仓库地址。这个方案本身没问题但它默认只发布当前模块的产物不会自动处理依赖传递。也就是说如果你的模块依赖了okhttp、gson或者团队内部的其他基础库别人依赖你的 aar 时这些间接依赖不会自动被拉取。要解决这个问题核心在于生成的.pom文件里必须包含dependencies节点声明当前模块依赖了哪些库、什么版本、什么 scope。而maven-publish插件在配合android-library使用时默认生成的 pom 文件往往是不完整的尤其是当你的依赖是通过api、implementation、compileOnly等不同配置引入的时候需要额外处理才能正确映射到 pom 的 scope 上。Artifactory 作为私服它本身只是一个存储和分发的角色不会帮你补全依赖信息。所以问题的关键不在 Artifactory而在于你推上去的 pom 文件是否完整、准确。1.3 整体方案选型与思路目前主流的做法有三种第一种是直接用maven-publish插件手动配置pom节点把依赖写进去。这种方式灵活但繁琐依赖多了容易漏。第二种是使用android-maven-publish这类第三方插件它能自动处理 Android 项目的变体variant和依赖映射减少手工配置。但第三方插件的维护状态需要关注版本兼容性也要留意。第三种是自定义 Gradle Task在发布前动态生成 pom 文件把api和implementation的依赖分别映射到 pom 的compile和runtimescope。这种方式最灵活但需要写不少 Gradle 脚本。我个人的选择是如果项目结构简单、依赖少用第一种就够了如果是多模块、依赖关系复杂的中大型项目推荐第二种或第三种。下面我会以maven-publish为基础结合自定义配置的方式来讲因为这样最能看清每一步在做什么也方便你根据自己项目的情况做调整。2. 核心细节解析与实操要点2.1 aar 与 jar 的本质区别在动手之前有必要先搞清楚 aar 和 jar 的区别因为这直接影响到依赖传递的处理方式。jar 文件只包含编译后的 class 文件和资源文件它不包含 Android 特有的资源如 layout、drawable、manifest 文件以及 proguard 规则。aar 文件则是一个完整的 Android 库归档里面包含了 classes.jar、res 目录、AndroidManifest.xml、R.txt、proguard.txt 等内容。这个区别带来的影响是aar 的依赖声明必须通过 pom 文件来传递而不能像 jar 那样简单地把依赖打进 fat jar。因为 aar 本身的结构决定了它不能把其他 aar 的内容合并进来否则资源冲突和 manifest 合并会出大问题。所以当你发布一个 aar 时如果它依赖了另一个 aar比如某个 UI 组件库这个依赖关系必须通过 pom 文件声明让 Gradle 在拉取你的 aar 时自动去拉取你依赖的那个 aar。这就是依赖传递的核心机制。2.2 依赖配置的 scope 映射规则在 Gradle 中Android 模块的依赖配置主要有以下几种api编译期和运行期都可见会传递给依赖方。对应 pom 中的compilescope。implementation仅当前模块可见不传递给依赖方。对应 pom 中的runtimescope。compileOnly仅编译期可见不参与打包不传递。对应 pom 中的providedscope。runtimeOnly仅运行期可见不参与编译会传递。对应 pom 中的runtimescope。这里有一个非常容易搞混的点implementation在 pom 中映射为runtimescope意味着依赖方在编译期看不到这个依赖但在运行期需要它。如果你的 aar 对外暴露的 API 中用到了某个依赖的类型比如方法参数或返回值那这个依赖必须用api引入否则依赖方编译时会报找不到类的错误。我踩过的一个坑就是某个基础库用implementation引入了gson但对外暴露的一个方法返回了JsonObject类型。结果依赖方在调用这个方法时编译器找不到JsonObject类直接编译失败。后来改成api引入才解决。2.3 Artifactory 仓库的路径规划在推送 aar 之前需要先规划好仓库的目录结构。Artifactory 支持 Maven 风格的路径布局通常的格式是{repo-name}/{group-id 路径}/{artifact-id}/{version}/{artifact-id}-{version}.aar举个例子假设你的 groupId 是com.example.libartifactId 是core-utils版本是1.0.0那么推送后的路径大致是libs-release/com/example/lib/core-utils/1.0.0/core-utils-1.0.0.aar同时还会生成对应的.pom文件和.module文件Gradle Module Metadata。这三个文件缺一不可尤其是.pom文件它承载了依赖传递的关键信息。注意有些团队会为 snapshot 版本和 release 版本分别建不同的仓库比如libs-snapshot和libs-release。在配置发布地址时要根据版本号后缀如-SNAPSHOT动态选择仓库地址避免把快照版本推到正式仓库里。2.4 版本号管理策略版本号的管理看似简单但在多人协作中很容易出问题。我见过的情况包括两个人同时发了一个1.0.0版本但内容不同、快照版本被误当成正式版本使用、版本号回退导致依赖方拉到旧代码。推荐的做法是正式版本使用三段式语义化版本号如1.2.3快照版本在正式版本号后加-SNAPSHOT如1.2.3-SNAPSHOT。每次发布正式版本时版本号必须递增不能覆盖已有版本。Artifactory 默认不允许覆盖同版本号的产物这个策略建议保持开启。另外可以在gradle.properties中统一管理版本号避免在多个地方硬编码。比如LIB_VERSION1.2.3 LIB_GROUPcom.example.lib然后在build.gradle中引用这些属性。这样改版本号只需要改一个地方减少出错概率。3. 实操过程与核心环节实现3.1 模块级 build.gradle 配置假设我们要发布的是一个名为core-utils的 Android Library 模块。首先在模块的build.gradle中应用maven-publish插件plugins { id com.android.library id maven-publish }接下来配置publishing块。这里的关键是afterEvaluate因为 Android 模块的变体信息如 release 变体的 aar 文件路径需要在评估完成后才能获取到afterEvaluate { publishing { publications { release(MavenPublication) { from components.release groupId project.LIB_GROUP artifactId core-utils version project.LIB_VERSION pom { name core-utils description Core utility library for Android url https://example.com/libs/core-utils licenses { license { name The Apache License, Version 2.0 url http://www.apache.org/licenses/LICENSE-2.0.txt } } developers { developer { id dev-team name Dev Team email devexample.com } } scm { connection scm:git:git://example.com/libs/core-utils.git developerConnection scm:git:ssh://example.com/libs/core-utils.git url https://example.com/libs/core-utils } } } } repositories { maven { url project.LIB_VERSION.endsWith(-SNAPSHOT) ? project.SNAPSHOT_REPO_URL : project.RELEASE_REPO_URL credentials { username project.ARTIFACTORY_USER password project.ARTIFACTORY_PASSWORD } } } } }这段配置做了几件事定义了发布内容来自 release 变体、设置了 groupId/artifactId/version、补充了 pom 的元信息许可证、开发者、SCM、根据版本号动态选择仓库地址、配置了认证信息。3.2 让 pom 文件包含依赖信息上面这段配置有一个问题from components.release生成的 pom 文件默认不包含依赖信息。也就是说如果你的core-utils依赖了okhttp这个依赖不会出现在 pom 里依赖方需要自己手动添加。要解决这个问题需要在pom块中手动添加依赖声明。但手动写死依赖列表有个问题当模块的依赖发生变化时pom 配置也要同步修改容易遗漏。更好的做法是动态读取模块的依赖配置自动生成 pom 的依赖节点。可以在afterEvaluate中遍历configurations.api和configurations.implementation把依赖映射到 pom 的 scope 上afterEvaluate { publishing { publications { release(MavenPublication) { from components.release // ... 其他配置 pom.withXml { def dependenciesNode asNode().appendNode(dependencies) configurations.api.allDependencies.each { dep - if (dep.group ! null dep.name ! null dep.version ! null) { def dependencyNode dependenciesNode.appendNode(dependency) dependencyNode.appendNode(groupId, dep.group) dependencyNode.appendNode(artifactId, dep.name) dependencyNode.appendNode(version, dep.version) dependencyNode.appendNode(scope, compile) } } configurations.implementation.allDependencies.each { dep - if (dep.group ! null dep.name ! null dep.version ! null) { def dependencyNode dependenciesNode.appendNode(dependency) dependencyNode.appendNode(groupId, dep.group) dependencyNode.appendNode(artifactId, dep.name) dependencyNode.appendNode(version, dep.version) dependencyNode.appendNode(scope, runtime) } } } } } } }这段脚本的逻辑是遍历api配置中的依赖映射为compilescope遍历implementation配置中的依赖映射为runtimescope。这样生成的 pom 文件就包含了完整的依赖信息依赖方拉取 aar 时会自动拉取这些间接依赖。注意configurations.api和configurations.implementation在遍历时可能会包含项目内部模块的依赖即project(:other-module)这类依赖的group和version可能为 null需要过滤掉。另外如果依赖是通过platform或constraints引入的也需要额外处理。3.3 处理传递依赖的版本冲突当你的 aar 依赖了okhttp:4.9.0而依赖方自己也依赖了okhttp:4.10.0时Gradle 默认会选择较高版本4.10.0。这个行为通常是符合预期的但有时候版本差异会导致 API 不兼容需要显式干预。可以在 pom 中添加exclusions节点排除某些传递依赖dependencyNode.appendNode(exclusions).appendNode(exclusion).with { appendNode(groupId, com.squareup.okhttp3) appendNode(artifactId, okhttp) }但更推荐的做法是在依赖方通过resolutionStrategy统一版本而不是在发布方强制排除。因为发布方无法预知依赖方的依赖树过度排除可能导致依赖方缺少必要的库。3.4 执行发布与验证配置完成后执行发布命令./gradlew :core-utils:publishReleasePublicationToMavenRepository如果一切正常你会在 Artifactory 的对应仓库路径下看到三个文件.aar、.pom、.module。接下来需要验证两件事第一检查 pom 文件内容是否完整。可以直接在 Artifactory 的 Web 界面查看或者用 curl 拉取curl -u username:password https://artifactory.example.com/libs-release/com/example/lib/core-utils/1.2.3/core-utils-1.2.3.pom重点看dependencies节点是否包含了你期望的依赖scope 是否正确。第二在一个独立的测试项目中引入这个 aar看是否能正常编译和运行dependencies { implementation com.example.lib:core-utils:1.2.3 }如果编译通过且运行时没有ClassNotFoundException或NoSuchMethodError说明依赖传递配置正确。3.5 多模块项目的批量发布如果项目中有多个模块需要发布逐个配置publishing块会很繁琐。可以在根项目的build.gradle中定义一个通用的发布脚本然后在各模块中引用。一种常见的做法是创建一个publish.gradle脚本文件放在根目录下内容包含通用的publishing配置。然后在需要发布的模块中apply from: rootProject.file(publish.gradle)在publish.gradle中通过project.name动态获取 artifactId通过project.LIB_VERSION获取版本号。这样每个模块只需要应用这个脚本不需要重复写配置。但要注意不同模块的依赖不同pom 中的依赖节点需要动态生成。所以publish.gradle中的pom.withXml部分要写成通用的遍历逻辑不能硬编码某个模块的依赖。4. 常见问题与排查技巧实录4.1 依赖方拉不到传递依赖这是最常见的问题。表现是依赖方引入了你的 aar编译时报找不到某个类而这个类来自你 aar 的某个依赖库。排查步骤检查 Artifactory 上的 pom 文件确认dependencies节点是否存在、是否包含缺失的依赖。如果 pom 中没有该依赖说明发布配置中的pom.withXml没有正确遍历到该依赖。检查该依赖是通过哪种配置引入的api、implementation、compileOnly等确认遍历逻辑覆盖了这种配置。如果 pom 中有该依赖但 scope 是runtime而依赖方在编译期需要用到该依赖的类型说明应该用api引入而不是implementation。如果 pom 中有该依赖且 scope 正确但依赖方仍然拉不到检查依赖方的 Gradle 配置中是否开启了transitive false或者是否有全局的依赖排除规则。4.2 版本号冲突导致运行时崩溃表现是编译通过但运行时抛出NoSuchMethodError或NoClassDefFoundError。原因通常是依赖方和自己的依赖树中出现了同一个库的不同版本Gradle 选择了较低版本导致缺少某些方法或类。排查方法在依赖方执行./gradlew :app:dependencies查看依赖树中该库的版本。如果发现版本低于预期可以通过resolutionStrategy强制指定版本configurations.all { resolutionStrategy { force com.squareup.okhttp3:okhttp:4.10.0 } }4.3 发布时认证失败表现是执行发布命令时返回 401 或 403 错误。排查步骤检查gradle.properties中的用户名和密码是否正确。检查 Artifactory 上的用户是否有对应仓库的写入权限。如果密码中包含特殊字符确认在gradle.properties中是否正确转义。如果使用的是 token 而非密码确认 token 是否过期。提示不要把用户名和密码硬编码在build.gradle中更不要提交到版本控制系统。推荐放在~/.gradle/gradle.properties或通过环境变量传入。4.4 快照版本被缓存导致拉不到最新代码表现是发布了新的-SNAPSHOT版本但依赖方拉到的还是旧代码。原因是 Gradle 默认会缓存快照版本一段时间通常是 24 小时。可以在依赖方添加配置configurations.all { resolutionStrategy { cacheChangingModulesFor 0, seconds } }这样每次构建都会检查快照版本是否有更新。但要注意这会增加构建时间建议只在开发阶段开启。4.5 常见问题速查表问题现象可能原因排查方向解决方案依赖方编译报找不到类pom 中缺少依赖声明检查 pom 文件的 dependencies 节点完善 pom.withXml 遍历逻辑运行时 NoSuchMethodError依赖版本冲突执行 dependencies 任务查看依赖树使用 resolutionStrategy 强制版本发布返回 401/403认证信息错误或权限不足检查用户名密码和仓库权限修正认证信息或联系仓库管理员快照版本不更新Gradle 缓存检查缓存策略设置 cacheChangingModulesForaar 文件缺失发布任务未执行或变体配置错误检查发布任务名称和变体确认 from components.release 配置pom 中依赖 scope 错误api/implementation 映射错误检查依赖引入方式调整依赖配置或映射逻辑4.6 几个我踩过的坑第一个坑在pom.withXml中遍历依赖时没有过滤掉project类型的依赖导致 pom 中出现了groupIdnull/groupId这样的无效节点。后来加了dep.group ! null的判断才解决。第二个坑某个模块用了compileOnly引入了一个注解处理器结果 pom 中把它映射成了compilescope导致依赖方也把这个注解处理器打进了包里。后来在遍历时排除了compileOnly配置。第三个坑发布到 Artifactory 后发现.module文件中的依赖信息和.pom文件不一致。原因是 Gradle Module Metadata 是由from components.release自动生成的而 pom 是手动配置的两者没有同步。解决方法是确保components.release中的依赖配置和 pom 中的一致或者直接禁用 Gradle Module Metadata 的发布。5. 进阶技巧与优化建议5.1 使用 Gradle Module Metadata 替代 pomGradle 从 6.0 开始默认发布 Gradle Module Metadata.module文件它比 pom 文件更精确地描述了依赖关系和变体信息。如果你的依赖方都使用 Gradle 构建可以考虑依赖.module文件而不是.pom文件。但要注意.module文件目前只有 Gradle 能识别Maven 和其他构建工具仍然依赖.pom文件。所以如果你们的依赖方可能使用 Maven还是需要保证 pom 文件的完整性。5.2 自动化版本号管理手动改版本号容易出错可以结合 CI 工具自动生成版本号。比如使用时间戳作为快照版本号version 1.2.3-${new Date().format(yyyyMMddHHmmss)}-SNAPSHOT或者使用 Git 的 commit hash 作为版本号的一部分。这样每次提交都能生成唯一的版本号避免覆盖问题。5.3 发布前的本地验证在推送到 Artifactory 之前可以先发布到本地 Maven 仓库进行验证./gradlew :core-utils:publishReleasePublicationToMavenLocal然后在本地测试项目中引入mavenLocal()仓库验证依赖传递是否正常。确认无误后再推送到远程仓库避免把有问题的版本推上去。5.4 依赖锁定与版本对齐对于多模块项目建议使用 Gradle 的platform或versionCatalog来统一管理依赖版本。这样在发布 aar 时pom 中的版本号也是一致的避免不同模块依赖了同一个库的不同版本。比如在根项目中定义一个platform模块声明所有依赖的版本dependencies { api platform(com.example.lib:platform:1.0.0) api com.squareup.okhttp3:okhttp api com.google.code.gson:gson }这样各模块引入依赖时不需要指定版本号由 platform 统一管理。发布 aar 时pom 中的版本号也会来自 platform保证一致性。5.5 处理 aar 中的 proguard 规则如果你的 aar 中包含 proguard 规则proguard-rules.pro这些规则会被打包到 aar 的proguard.txt文件中。依赖方在开启混淆时Gradle 会自动合并这些规则。但要注意如果你的规则中有-keep指令可能会和依赖方自己的规则冲突。建议在 aar 的 proguard 规则中只保留必要的 keep 规则尽量使用-keepclassmembers而不是-keep减少对依赖方的影响。5.6 监控 aar 体积aar 的体积直接影响依赖方的包大小。建议在发布前检查 aar 中是否包含了不必要的资源或类。可以通过./gradlew :core-utils:assembleRelease生成 aar然后用解压工具查看内容。常见的体积问题包括包含了未使用的资源文件、包含了测试代码、包含了重复的类。可以通过shrinkResources和minifyEnabled来优化但要注意这些配置可能会影响 aar 的功能完整性。6. 个人实操体会这套流程我在多个项目中反复用过最大的感受是依赖传递的问题往往不是技术难题而是配置细节的遗漏。pom 文件中的每一个节点、每一个 scope 映射都可能在某个特定场景下成为关键。我的建议是每次发布 aar 后都用一个干净的测试项目验证一遍依赖传递是否正常。不要假设配置没问题因为 Gradle 的依赖解析规则在不同版本、不同配置下可能有差异。另外团队内部最好有一套统一的发布规范和检查清单包括版本号命名规则、pom 必填字段、依赖 scope 映射规则等。这样即使换人维护也能保证发布质量的一致性。最后分享一个小技巧如果依赖方反馈拉不到某个传递依赖可以让他们在build.gradle中添加以下配置打印出依赖解析的详细信息configurations.all { resolutionStrategy { eachDependency { details - if (details.requested.group com.example.lib) { println Resolving: ${details.requested.group}:${details.requested.name}:${details.requested.version} } } } }这样能快速定位是哪个环节出了问题比盲目猜测高效得多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑