
JCenter 远程仓库停止维护这件事说起来已经不算新闻但直到今天我打开任何一个老项目或者接手别人的历史代码时依然能频繁撞见一大串Could not resolve的报错最后追根溯源发现是某个传递依赖还死死捆在jcenter()上。如果你现在项目里还留着jcenter()这个声明或者你经常要维护那些从 Android Studio 早期模板一路传下来的工程那这篇文章就是给你写的。我会把 JCenter 停维护的来龙去脉、Gradle 依赖解析的原理、以及从配置迁移到问题排查的完整实操都过一遍争取让你看完之后能直接上手处理。1. 先搞清楚 JCenter 停服到底停掉了什么1.1 JCenter 在项目里扮演什么角色JCenter 本质上是一个面向 Java/JVM 生态的 Maven 仓库和 Maven Central 一样托管了大量开源库的 jar、aar 以及对应的 POM 元数据。它在 Android 开发早期非常流行很多第三方库只在 JCenter 上发布正因为这个生态惯性Android Studio 早期的工程模板也曾经把jcenter()写成默认远程仓库。那个年代的项目打开根目录的build.gradle几乎必然能看到这么一段allprojects { repositories { google() jcenter() mavenCentral() } }这种写法在当年没什么问题因为构建时 Gradle 会按顺序去这些仓库找依赖找不到就换下一个。问题在于JCenter 停止维护之后这个仓库的定位从“还能用”变成了“只读”甚至“随时可能消失”而大量老项目依然把jcenter()当成默认依赖来源来使用。你表面上只是在构建实际上等于把一个已经宣布退役的服务器当作生产依赖源这就像把公司文件放在一个已经歇业的库房里平时没人查的时候一切正常某天突然要拿一份关键合同才发现库房已经封了。1.2 停止维护的时间线与真实影响范围时间线大概是这样的官方在 2021 年发布公告明确计划停止 JCenter 的维护并且很快就不再接受新版本和新包的提交仓库进入只读状态之后又经历了多次时间点调整外界一度以为仓库完全关闭但最后它以只读的形式继续保留了一段时间。整个过程最大的迷惑点就在这里它不是某一天零点准时消失而是“慢慢变得不可靠”。对使用者的影响就非常具象了构建时提示某个依赖在jcenter()中找不到但明确知道这个依赖以前存在下载依赖时连接超时、返回 403、证书异常阻塞全部构建流程CI 机器上因为缓存是干净的第一次拉取就直接失败而本地开发机因为 Gradle 缓存里已经存了旧依赖编译反而一直正常项目里既有google()又有mavenCentral()但因为jcenter()还写在仓库列表中间Gradle 在解析某些库时优先去 JCenter 碰运气哪怕 Maven Central 里明明有同款坐标也会被这个“半死不活”的仓库卡住。这最后一点是最坑的。很多开发者以为只要我没主动用 JCenter 的库就行但实际上只要仓库声明还挂在配置里Gradle 的解析过程中就可能花时间访问它甚至在部分异常场景下导致解析中断。所以这不仅仅是“把库搬走”还得把所有指向 JCenter 的配置清理干净。2. 依赖解析机制和“为什么断”的原理2.1 Gradle 从远程仓库拉依赖的完整链路要理解为什么 JCenter 停止维护会造成连锁反应得先把 Gradle 拉依赖的过程捋一遍。当我们写下implementation com.example:example-lib:1.2.3时Gradle 会把这个坐标拆成 group、artifact、version 三部分然后在配置阶段按顺序检查每一个远程仓库。具体链路是这样的首先检查本地缓存目录比如~/.gradle/caches/modules-2/files-2.1下有没有对应坐标的缓存如果没有就向仓库发起 HTTP 请求获取该组件的 POM 文件POM 文件里写明了这个库自己需要的传递依赖Gradle 还要继续逐个去解析那些传递依赖所有依赖都找到之后再下载真正的 jar 或 aar 到本地。任何一个环节失败构建就会报错并且经常把所有失败信息叠加在一起看起来非常吓人。所以当 JCenter 已经不可写、部分内容不可读、或者干脆连服务器都不稳定时它影响的不只是“某个库拿不到”还包括“依赖解析过程中需要花大量等待时间”和“传递依赖链断裂”。老项目的依赖树往往又深又乱一个库背后挂着十几个传递依赖只要其中有任何一个只在 JCenter 上发布整个构建就会失败。2.2 仓库优先级和只读模式下的行为差异仓库顺序在 Gradle 里非常重要。各远程仓库会按照配置的先后顺序被逐个尝试一旦某个仓库成功返回了 POMGradle 通常就不会再询问后面的仓库了。这就解释了为什么google()一般要放在最前面因为 AndroidX、Google Play services 这类官方库必须从 Google 仓库拿如果不放最前面Gradle 可能会从 Maven Central 里解析到一个陈旧或不完整的版本。而 JCenter 进入只读模式之后行为变得非常微妙。对于 JCenter 上原本就有的库它还能正常返回元数据对于那些根本不在 JCenter 上的库它会返回 404。问题在于很多配置里没有把仓库区分清楚Gradle 会先去 JCenter 碰壁再跑去 Maven Central 找。一次两次没问题但在网络状况不佳、或者 JCenter 进入不稳定阶段后这种多余的访问会显著拖慢构建甚至直接导致解析失败。更隐蔽的是如果某个库的 POM 文件里声明了它只存在于 JCenter那么即使这个库已经发布了新版本到 Maven CentralGradle 也可能因为 POM 里的依赖声明没有更新继续去旧的仓库解析老版本。这类问题不是简单换仓库就能解决的需要配合依赖版本管理来处理。3. 先做体检判断项目是否真的依赖 JCenter3.1 从配置里找到 jcenter() 声明迁移之前第一步就是给项目做一次全面体检把所有jcenter()的引用的找出来。位置通常不只在根目录的build.gradle还包括根项目的build.gradlesettings.gradle或settings.gradle.kts多模块项目的每个子模块build.gradle通过buildscript块声明的插件仓库一些公共的gradle脚本文件最直接的办法是在项目根目录执行搜索grep -rn jcenter() --include*.gradle --include*.gradle.kts .然后逐个文件确认。新版 Gradle 推荐把仓库配置统一收敛到settings.gradle的dependencyResolutionManagement块中所以搜索到的位置可能更集中。老项目则经常分散在各模块的repositories里搜出来的结果可能很多不要怕麻烦一个一个处理。找到声明只是第一步需要留意jcenter()出现的位置。如果是buildscript里的插件仓库意味着某些 Gradle 插件本身是从 JCenter 下载的如果出现在allprojects或dependencyResolutionManagement里影响的是项目所有依赖如果只出现在某个模块里就只影响这个模块。处理优先级上建议先处理allprojects和settings因为影响面最大。3.2 用依赖树和构建日志定位“漏网之鱼”光靠搜索还不够因为有些传递依赖是间接引入的。你根本不知道哪个库在 POM 里写了jcenter()的仓库地址或者哪个依赖只在 JCenter 上才有。这时候可以用 Gradle 自带的依赖树分析命令./gradlew :app:dependencies --configuration debugRuntimeClasspath这个命令会输出一份非常完整的依赖树能看见每个直接依赖底下挂着哪些传递依赖。如果某个节点后面标注了仓库来源或者解析过程提示来自 JCenter那就很值得关注了。不过依赖树很长更高效的做法是先尝试构建然后看报错信息里最先出现的那几个依赖坐标。构建失败时Gradle 通常会输出类似Could not resolve com.example:example-lib:1.2.3. Could not get resource https://jcenter.bintray.com/com/example/example-lib/1.2.3/example-lib-1.2.3.pom.这时候就能直接看到是哪个依赖、从哪个仓库地址解析失败。拿到这个坐标后再去查它是否在 Maven Central 上存在替代版本。我习惯的做法是把所有解析失败的坐标统一收集起来列成一个清单然后逐个判断是单纯迁移、升级还是需要重新发布到私有仓库。4. 迁移到 Maven Central 的完整操作4.1 替换仓库声明并修正仓库顺序确认项目依赖范围后就可以开始动手改配置了。对于绝大多数依赖只要把jcenter()换成mavenCentral()问题就能解决大半。新版项目建议统一在settings.gradle里管理仓库dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() } }如果是老项目还在用allprojects也要同步修改allprojects { repositories { google() mavenCentral() } }这里有一个容易被忽略的点repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)会禁止项目模块里单独声明仓库强制统一走中央配置。这个限制对大型项目是件好事能防止某个模块私自加一个奇怪的仓库地址但它也会让一些老项目在迁移时直接报错。如果你不想一步到位可以先设置成PREFER_PROJECT或干脆不加这一行等所有仓库都收敛到中央配置后再开启严格模式。另一个重点是把google()放在mavenCentral()前面。Google 官方库在 Android 项目里优先从 Google 仓库解析能够避免部分库被中央仓库的老版本覆盖。顺序错了构建通常也能过但遇到某些版本兼容问题时会特别难排查。注意jcenter()不能想当然地直接删除一定要先确认所有依赖都能从其他仓库解析到。如果某个依赖只有 JCenter 有删掉之后会让原本能构建的项目立刻失败所以迁移之前最好先把依赖清单拉出来核对一遍。4.2 只在 JCenter 存在的老库怎么办迁移过程中最麻烦的其实是那批“只有 JCenter 上有、Maven Central 没有”的库。这类库通常是因为作者早年只往 JCenter 发布后来项目不维护了也没同步到中央仓库。遇到这种依赖有几种处理思路。第一种是升级替代库。去官方仓库或者开源平台看看这个库是不是已经换新坐标或者有没有功能相同的后继库。比如某些老库已经停止维护官方推荐用另一个全新的库替代这时候不要犹豫花点时间改代码比在构建配置里硬扛强得多。第二种是把老库手动迁移到私有仓库。对于实在找不到替代的库最稳妥的做法是下载到对应版本的 jar/aar 和 POM 文件然后上传到企业内部自建的 Maven 私有仓库中。上传时一定要把 POM 一并传上去因为 POM 里记录了传递依赖只传 jar 会导致下游项目解析依赖时出错。自建仓库的工具选择很多诸如 Nexus 一类的开源仓库管理工具就可以胜任我这里不具体展开安装步骤反正思路是让私有仓库成为企业内部统一依赖入口。第三种是短期应急方案在项目的libs目录下放本地 jar/aar然后通过flatDir仓库引用。比如repositories { flatDir { dirs libs } }这个方案能解决最眼前的构建问题但副作用很大。flatDir不支持 POM 和传递依赖解析也拿不到模块元数据版本管理基本靠自觉后续维护成本很高。我建议只把它当成临时补丁不要长期依赖。4.3 用私有仓库或镜像仓库做平滑过渡对很多团队来说一次性把所有依赖从 JCenter 迁到 Maven Central 是有风险的尤其是项目历史长、依赖数量大、网络环境又不稳定的情况。这时可以分两步走先让私有仓库接管 JCenter 的“库存”再把项目仓库配置改成“私有仓库优先 中央仓库兜底”。操作思路是这样的在私有仓库管理端先配置一个代理仓库代理源指向 Maven Central同时把之前从 JCenter 上下载过的所有老依赖手动导入。然后项目的repositories配置改成repositories { google() mavenCentral() maven { url http://你的私服地址/repository/maven-public/ allowInsecureProtocol true } }如果私服支持 HTTPS就不需要allowInsecureProtocol true。这个过渡方式的优势是所有依赖都从内网拉取速度更快也能绕过 JCenter 关闭后带来的各种连接异常。等确认业务代码不再依赖任何 JCenter 的老库之后再逐步把中央仓库的地址也收敛到私服代理这样既统一了入口又方便审计。5. 实操中常见问题与排查口诀5.1 解析失败的典型报错与定位方法迁移过程里会遇到各种各样的报错我整理一个高频问题对照表方便你快速定位报错特征可能原因处理建议Could not resolve com.example:lib:1.0.0该坐标在当前仓库中不存在或只存在于 JCenter去 Maven Central 搜索替代版本无法替代则导入私有仓库Could not GET https://jcenter.bintray.com/...仓库声明残留构建仍在访问 JCenter全局搜索并移除jcenter()替换为mavenCentral()Received status code 403 from serverJCenter 或某些镜像拒绝访问或私服权限校验失败检查仓库地址和凭据无权限则改用其他可用仓库SSLHandshakeException/ 证书问题私服或镜像的 HTTPS 证书不受信任配置allowInsecureProtocol前先确认网络安全性必要时升级证书Could not find a matching dependency传递依赖只存在于 JCenter查看依赖树锁定传递源升级或手动指定新版本RepositoriesMode.FAIL_ON_PROJECT_REPOS相关报错子模块里私自声明了仓库把子模块仓库统一迁移到settings.gradle中央配置定位问题是关键。不要被 Gradle 输出的“无法解析所有配置”这种大而全的总结句吓到往上翻找到What went wrong后面的核心原因尤其是Caused by部分。那里才会写出真正失败的依赖坐标和仓库地址。5.2 缓存、认证、传递依赖的几个坑我发现很多开发者对 Gradle 缓存有误解觉得只要报错就是环境问题跑一遍--refresh-dependencies就好了。实际上这个参数只会强制刷新动态版本比如1.、SNAPSHOT和 POM 元数据对于已经缓存的固定版本它并不会重新下载。如果你的本地缓存里已经有那个依赖那构建还是会通过并不能模拟干净环境下的问题。想真正复现 CI 场景要么删掉~/.gradle/caches对应模块要么在有隔离环境的工作机器上跑一次。传递依赖是另一个重灾区。假设 A 库已经迁移到 Maven Central但 A 的 POM 里还声明依赖 B而 B 只在 JCenter 上有那你的项目即使配置了mavenCentral()也会挂。处理方式有几种如果 B 有新版并已经上传 Maven Central可以在项目里显式声明implementation com.example:b:2.0.0Gradle 会优先用较新的显式版本覆盖传递版本如果 B 没有新版可以用resolutionStrategy强制指定版本configurations.all { resolutionStrategy { force com.example:b:1.5.0 } }但前提是你得确保这个版本能从当前仓库解析到否则越强制越糟。认证问题也不得不提。如果你迁移之后用私有仓库而又需要在构建时提交用户名和密码千万不要直接在build.gradle里写死。正确做法是通过环境变量或gradle.properties读取把凭据文件加入版本控制忽略列表。这一点我在很多团队审查代码时都看到过包括把私服密码明文提交到仓库的案例一旦仓库源码泄漏内部系统就跟着暴露了。5.3 迁移后的构建验证清单配置改完之后我通常不会直接上生产而是先执行一套验证步骤./gradlew clean ./gradlew :app:assembleDebug如果报错优先看仓库地址是否已经完全去掉 JCenter再看构建日志里是否还出现jcenter.bintray.com的字样。确认没问题后把所有模块的任务都跑一遍./gradlew build有条件的再多跑一次./gradlew --refresh-dependencies build这样可以确认依赖从新仓库重新解析时也能成功而不是依赖本地残留缓存。整个过程有点枯燥但值得做毕竟构建系统是开发流程的地基地基不稳后续什么自动化都白搭。6. 长期维护经验从这次迁移里养成的几个习惯6.1 依赖锁定与统一版本管理这次迁移给我最大的教训就是项目依赖不能一家独大更不能长期挂在一个已经停止维护的仓库上。所有项目都应该尽快把依赖版本管理做起来。Gradle 7.0 以后推荐使用 Version Catalog版本目录在gradle/libs.versions.toml里统一管理版本号让依赖坐标的变更集中在一个文件里而不是散落各模块。更进一步可以启用依赖锁定。Gradle 的lockfile机制能记录已解析的依赖版本和来源防止未来某个传递依赖悄悄变更导致构建结果不可复现。在项目根目录执行./gradlew dependencies --write-locks生成 lockfile 后每次构建都会校验锁定状态任何不在锁定列表里的依赖变更都会直接报错。对于需要长期维护的项目这个功能相当值得开启等于给依赖上了一份保险。6.2 定期审计仓库源与依赖健康度依赖管理不是一次性的事仓库地址可能因为某个同事的顺手修改又多了奇怪的源或者某个依赖的官方版本已经迁移到了新的坐标但项目还在用老坐标。我的习惯是写一个小脚本定期扫一遍所有*.gradle和*.gradle.kts文件里的repositories块把所有仓库地址输出出来人工确认一遍确保里面没有任何已停止维护的旧仓库。grep -rn maven {\\|mavenCentral\\|google\\|jcenter --include*.gradle --include*.gradle.kts . | grep -v ^Binary另外我还会检查依赖树里是否出现一些明显没人维护的老库用 version catalog 定期更新插件和核心依赖版本。JCenter 停止维护这种事本质上是一次对工程健康度的大考。那些平时依赖管理规范的项目迁移时只需要改两行配置那些长期依赖缓存、凭感觉加依赖的项目就会被一堆老库拖住很久。我个人在这轮迁移中最后悔的事情就是早期没把仓库源当作代码资产来管理。JCenter 还在时它掩盖了很多依赖相关的坏味道比如不规范的版本号、缺失的 POM、混乱的传递依赖。当它真正不可靠之后这些问题才集中暴露出来。所以如果你现在就在处理类似迁移不用焦虑把每个依赖都当成一个需要交代的接口来对待一个坐标一个坐标查清楚来源会比盲目改配置靠谱得多。走过这轮之后你的项目反而会变得更干净、更可控这也是坏事变成好事的地方。