尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Gradle依赖解析失败排查:Unable to resolve dependency的根治思路

Gradle依赖解析失败排查:Unable to resolve dependency的根治思路 昨天下午我打开Android Studio准备同步一个搁置了一阵子的外部项目Sync的进度条刚走两圈Build窗口直接刷出一排红色ERROR开头第一行就是ERROR: Unable to resolve dependency for :appdebug/compileClasspath: Could not resolve com.android.support:appcompat-v7:28.0.0。后面跟着Failed to resolve和一堆Required by: project :app。说实话这个报错在Android开发里算不上冷门——但凡你拉过老项目的代码、升级过AS版本、或者换了台新机器重新clone仓库撞上的概率都不低。网上搜这个错铺天盖地的答案都指向同一个动作重新下载Gradle。方向不假但真按它操作会发现光重下Gradle并不总能把同步跑通因为你得先搞清楚到底坏在哪一环。这篇文章我就把自己在项目里反复遇到这个报错之后的完整排查过程捋一遍顺便把重新下载Gradle这个动作讲透什么时候它有效什么时候它治不了以及怎么操作才不白折腾。1. 报错现场还原一行红色ERROR背后的三层含义1.1 一次典型的Unable to resolve dependency长什么样AS里的报错通常长这样ERROR: Unable to resolve dependency for :appdebug/compileClasspath: Could not resolve com.github.xxx:library:1.2.3. Could not resolve com.github.xxx:library:1.2.3. Required by: project :app Failed to resolve: com.github.xxx:library:1.2.3这一段文本信息量不小拆开看:appdebug/compileClasspath表示当前要解析的是app模块的debug编译期classpath也就是说这次同步卡在了编译前的依赖准备阶段。Could not resolve com.github.xxx:library:1.2.3Gradle在你声明的所有仓库里都尝试找这个坐标但结果不理想——要么压根没有要么下载不下来。Required by: project :app定位到了事故源头是app模块的build.gradle里写明了依赖它。实际使用中报错列表往往不止一个依赖而是一串。这很容易造成系统崩了的错觉其实经常只是第一个依赖解析失败后Gradle停止了后续的解析流程把跟它相关的一批依赖全部标红。碰到这种情况别慌重点关注第一条根因大多在它身上。1.2 先分清找不到和下不了排查方向完全不同这是我踩过几次坑之后养成的第一个习惯看到这类报错第一件事不是去找镜像、不是去下Gradle而是分辨它属于哪种失败。两类情况的报错文案有明显差异报错片段问题本质排查方向Could not find com.xxx:yyy:1.0仓库里根本没这个坐标或者仓库列表里没包含它所在的仓库检查依赖坐标、检查repositories声明Could not download ... Connection timed out仓库里有货但你的网络访问不到换网络、换镜像、检查代理Checksum mismatch / SHA-256 ...本地缓存损坏清理对应缓存条目Could not resolve ...无附加信息依赖已被仓库下架或者版本号和仓库不匹配改版本号、迁移坐标如support转AndroidX打个比方找不到是门店里压根没上架这个货下不了是门店有货但你的快递送不到。两个问题拧的不是同一颗螺丝。我最多的一次一个Could not find org.jetbrains.kotlin:kotlin-stdlib:1.3.50让我查了半天项目依赖最后发现是Kotlin插件版本比依赖版本新某私有仓库的几个版本因为发布时间差没有同步到位。版本号本身没错但仓库里就是没有这个文件。1.3 为什么Gradle发行版会跟依赖解析错误搅在一起严格来说Unable to resolve dependency针对的是项目依赖而Gradle发行版distribution是构建工具本身。gradle wrapper负责拉取Gradle依赖解析是Gradle跑起来之后才做的工作两者不是同一个东西。但实际体验里它们经常一起出问题。最常见的一种连环事故gradle-7.x-all.zip这种all发行版体积不小网络稍微波动就下载到一半断掉本地只剩下.part临时文件。之后再次触发Sync时Gradle发现这个zip不完整会尝试重新下载如果网络再次失败构建过程早期就卡死后续一切解析工作都做不了AS界面上就会冒出各种Unable to resolve dependency的衍生报错。字面是依赖问题但根子是Gradle发行版损坏。所以重新下载Gradle作为解法没毛病前提是你确实确认到了这一层——如果根本没到发行版这一层重下多少次都一样。2. 先把原理盘明白Gradle、仓库配置和AGP三方是怎么配合的2.1 Gradle的查找-下载-缓存-校验闭环要理解这个报错得先知道Gradle执行依赖解析时的完整过程。我尽量用大白话描述读取settings.gradle确定项目包含哪些模块。读取每个模块的build.gradle把dependencies块里声明的所有依赖列出来。读取repositories块确定可以去哪些仓库找货。对每个依赖按pom文件 → jar/aar文件 → 源码或javadoc的顺序向仓库发HTTP请求。按照仓库声明的顺序挨个询问命中即停。下载成功后写入本地缓存默认在~/.gradle/caches/modules-2/files-2.1。后续构建直接复用缓存不再发网络请求。如果第5步所有仓库都返回404你会看到Could not find如果第6步超时、断连或者写到一半失败你会看到Could not download或者是ambiguous的Could not resolve。这个过程类似于你在几个外卖平台搜索同一家店Gradle像是一个不知疲倦的跑腿员一家一家平台点进去搜全部没货就回来告诉你买不到如果某一家的接口挂了它也会告诉你这个平台访问不了。2.2 repositories配置的坑比你想象的多很多老项目的仓库配置长这样repositories { google() mavenCentral() maven { url https://jitpack.io } }三个常见仓库各有分工google()指向maven.google.com托管AndroidX、AGP以及大部分Google自家库。mavenCentral()指向repo.maven.apache.org / repo1.maven.org老牌中央仓库大量Java和Kotlin库都在这里。maven { url https://jitpack.io }JitPack仓库很多GitHub项目的依赖通过它快速构建。容易出问题的点有几个。第一老项目里的jcenter()。JCenter已经停止更新虽然服务器还活着但新版本依赖基本不会进去。一个写了jcenter()的老项目拉一个新库坐标时大概率解析失败。我的处理方式是把jcenter()从repositories里去掉换成mavenCentral()然后逐个验证依赖是否在Central里有对应版本老版本coord如果在JCenter独有就得专门加maven { url https://jcenter.bintray.com }这种历史地址但这只是临时续命方案。第二私有仓库缺失。很多公司内部库放在私有Nexus或者Artifactory上项目里私有仓库的地址写在build.gradle或settings.gradle里。你换了一台新机器clone下来的项目代码可能根本没有这个仓库地址或者被gitignore漏掉了。结果就是公司内网能解析的依赖你这边全部Unable to resolve dependency。这种情况不是Gradle的问题更不是该重下Gradle而是要把私有仓库配置补上。第三AGP 7.0之后的仓库策略变化。新版AGP默认通过settings.gradle里的dependencyResolutionManagement统一管理仓库项目级别的repositories声明会被禁用或提示失败。团队里如果项目升级过AGP版本仓库配置要从build.gradle挪到settings.gradle这一步漏了原本好好的依赖也会一夜之间全部解析失败。2.3 AGP版本和Gradle版本是绑定关系别乱升级AGPAndroid Gradle Plugin本质上是Gradle的一个插件它对Gradle版本有硬性约束。比如项目里AGP是7.4你非把Gradle降到6.x那Gradle自己就会罢工报出Minimum supported Gradle version is ...之类的错。如果在Gradle版本和AGP不匹配的前提下强行Sync可能出现各种莫名其妙的解析异常。所以排查依赖问题前有个固定动作不能省打开gradle/wrapper/gradle-wrapper.properties看Gradle版本打开项目根目录build.gradle看AGP版本再对照官方兼容表确认两者没有互相超越底线。这张表我在后面第5章会放一个常用版本对照顺手收藏能省很多事。3. 从浅到深逐层排查我们按这个顺序动手3.1 第一步复现并抓取完整报错现场拿到报错之后我的建议是先别急着清缓存或者重下Gradle而是把问题稳定复现一次。第一次Sync失败可能是网络瞬时抖动重新跑一次Sync看它是不是稳定重现。如果Build窗口的进度条停在Configuration阶段很久不动说明Gradle正在下载依赖或发行版这时候需要的是等待而不是反复点Sync。复现时把报错文本完整复制到编辑器里别只看AS的友好提示框。我常用的操作是直接打开终端跑命令./gradlew clean assembleDebug --stacktrace命令行能看到Gradle真实的执行细节比AS界面上过滤过的信息多得多。如果能在命令行稳定复现同一个错误说明这个问题是配置或网络层面的跟AS的缓存索引关系不大。有一个判断技巧如果构建在几秒内秒失败基本可以认定是某种静态配置问题——仓库没配全、坐标拼错、版本不存在或者AGP/Gradle版本不匹配如果卡住不动再慢慢报超时那多半是网络链路问题。3.2 第二步检查repositories与依赖坐标这一步是排查成功率最高的环节。我每次都按以下顺序检查先打开settings.gradle看仓库声明是否完整。一个标准的现代项目配置长这样dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() maven { url https://jitpack.io } } }再检查报错里出现的group:name:version是否真实存在于仓库。判断方法不复杂对于普通公共库直接在浏览器访问仓库目录搜索比如repo.maven.apache.org搜一下坐标对于Google系依赖去maven.google.com查。如果这个版本号在仓库里压根不存在那就把版本改成真实存在的稳定版试试。还有一种常见场景是版本号存在但它只在JitPack这种动态构建仓库里。JitPack有一个特点——首次请求某个GitHub坐标版本时它需要在后台现场编译编译完成之前你直接解析会失败。这种依赖需要在JitPack官网搜到对应项目点一下Get按钮触发构建然后再回到AS重新Sync。排查到这如果确认坐标和仓库配置都没问题但还是解析失败就要考虑是不是版本已下架。最典型的例子是support库。com.android.support:appcompat-v7:28.0.0在Google仓库已经不再是新项目的推荐坐标AndroidX取而代之。某些support库的历史版本在仓库里还能找到但配套artifact可能已经下架。这种情况唯一的出路是迁移——要么升AGP版本后把support依赖改成AndroidX坐标要么在老环境里固定老版本Gradle和老版本AGP不再升级。3.3 第三步清理本地缓存中的坏条目确认网络正常、仓库可访问之后如果还是失败那就要怀疑本地缓存。Gradle的依赖缓存默认在~/.gradle/caches核心目录是modules-2/files-2.1所有下载好的依赖都按group/name/version存放。项目目录下的.gradle是配置缓存和工作目录删掉影响不大但前者删掉就麻烦了——它会让Gradle把全部依赖重新下一遍。清理缓存的操作方式我按优先级推荐精准删除进入~/.gradle/caches/modules-2/files-2.1找到出问题依赖对应的group/name/version整个目录删掉重新Sync。全局清理把整个modules-2目录删除让所有依赖重新下载。这个方案对网络要求高适合依赖确实集体损坏时使用。项目清理./gradlew clean清的是项目构建产物不涉及Gradle缓存但对解决依赖问题意义不大别指望它。AS菜单里的Invalidate Caches / Restart注意这一步只重置AS的索引和IDE状态不清理Gradle的modules缓存。很多人以为点过它就等于清了缓存其实对依赖损坏问题基本无效。缓存损坏的特征通常在报错里带着Checksum、SHA-256或者Blocked mirror for repositories字样。我遇到过最典型的一次是从内网设备拷来一份残缺的.gradle目录之后反复报Could not resolve com.android.tools.build:gradle:3.4.1打开缓存目录一看那个版本的.part文件还躺在里面。把坏条目删掉让Gradle重新下载一次过。3.4 第四步切到命令行看真实的网络请求如果上面的操作都做了还不通那就需要更底层的定位了。命令行有两个命令非常有用。第一个是查看依赖树./gradlew :app:dependencies --configuration debugCompileClasspath --stacktrace输出里会列出app模块的完整依赖树解析失败的节点会直接标出错误原因比AS的报错列表更直白。第二个是查看Gradle执行过程中的具体请求./gradlew help --info--info级别会打印Gradle发起的HTTP请求目标URL可以清楚看到它卡在哪个仓库上。为了区分是Gradle的问题还是网络的问题我还会用curl直接模拟请求一次curl -I https://repo.maven.apache.org/maven2/commons-io/commons-io/2.11.0/commons-io-2.11.0.pom如果curl能正常访问而Gradle超时那么问题大概率出在AS的代理设置或Gradle的JVM网络配置上。AS里Settings Appearance Behavior System Settings HTTP Proxy是个容易踩坑的地方手动代理配置填错IP或端口会导致所有仓库请求全部失败而且报错形式五花八门有时就是简单的Unable to resolve dependency。检查代理模式是不是No proxy再确认Gradle JDK版本Settings Build Tools Gradle Gradle JDK是否匹配项目要求。走到这一步基本已经把问题分成了两半要么是仓库、坐标、AGP版本这类配置问题要么是网络链路问题。如果是后者才轮到重新下载Gradle这个动作上场。4. 重新下载Gradle治标时怎么做得彻底4.1 先确认wrapper指向再看dists目录残留重下Gradle前先打开gradle/wrapper/gradle-wrapper.properties确认项目要求的版本。一个典型配置长这样distributionBaseGRADLE_USER_HOME distributionPathwrapper/dists distributionUrlhttps\://services.gradle.org/distributions/gradle-7.5-bin.zip networkTimeout10000 validateDistributionUrltrue zipStoreBaseGRADLE_USER_HOME zipStorePathwrapper/dists本地对应目录在~/.gradle/wrapper/dists/gradle-7.5-bin/hash/。打开这个目录看如果有gradle-7.5-bin.zip.part和*.lck文件说明下载中断了这就是标题说的重新下载Gradle最典型的场景。如果有gradle-7.5-bin.zip但没看到解压后的gradle-7.5目录说明下载刚完成还没触发解压重开同步通常能自己搞定。如果既有zip又有解压目录但构建仍然异常可能是解压出来的文件不完整需要删掉整个目录重新来。还有一个标志性报错看到它基本可以锁定是Gradle发行版问题Could not install Gradle distribution from https://services.gradle.org/distributions/gradle-7.5-bin.zip这句的意思很直白wrapper想从指定地址下载Gradle结果没成功。这个错是重新下载Gradle这个解法直接适用的场景。4.2 直接用国内镜像替换distributionUrlGradle官方地址services.gradle.org在部分网络环境下下载速度很不稳定这是老开发都懂的问题。针对这种情况一个高效的解法是把distributionUrl换成国内镜像地址让Gradle从更近、更快的节点拉取发行版。腾讯云镜像distributionUrlhttps\://mirrors.cloud.tencent.com/gradle/gradle-7.5-bin.zip阿里云镜像distributionUrlhttps\://mirrors.aliyun.com/gradle/gradle-7.5-bin.zip我个人的习惯是优先试腾讯云不行再换阿里云两个都不行就回到手动离线包方案。改完URL后有一个隐藏的好处distributionUrl一变Gradle根据它计算出的hash目录名也会变等于绕开了旧的损坏目录不用手动删.part文件也不用清理.lck。有一个细节容易忽略编辑gradle-wrapper.properties时URL里的冒号前面要保留反斜杠转义。原始文件写的是https\://...如果你手动改成https://...漏了反斜杠Gradle会把URL解析出错。这个错误很小但报错形式很容易让人误以为又是依赖问题。4.3 手动下载离线包并放置到正确位置网络环境实在不配合的情况下手动下载离线包是保底方案。操作流程如下在浏览器或其他机器上下载对应版本的zip来源可以是官方地址也可以是镜像站。确认GRADLE_USER_HOME的位置。macOS/Linux是~/.gradleWindows是C:\Users\用户名\.gradle。进入wrapper/dists目录找到gradle-7.5-bin路径。如果这个目录不存在就先手动创建对应的目录结构。直接一股脑把zip塞进去是不行的——它会放在一个特殊的hash子目录里这个hash是根据distributionUrl生成的base36编码肉眼算不出来。我提供一个土办法先保留wrapper配置不变触发一次Sync让Gradle自动创建目录开始下载然后立刻关掉AS去dists目录里看它自动生成的hash文件夹名把下载好的zip放进去。删除目录里的*.lck和*.part文件重新打开AS触发Sync。Gradle会自动校验zip完整性并解压。放进去的zip一定要完整。Gradle会对zip做校验如果发现文件不完整它会无视你这个离线包重新走下载流程。校验方法很简单对照官方或镜像站标注的文件大小差不多才能安心放进去。还有一种更省事的做法不依赖wrapper直接在AS里指定本地Gradle发行版。路径是Settings Build Tools Gradle Use local gradle distribution然后填一个已经解压好的Gradle目录路径。这个方法完全绕开wrapper下载环节本地调试非常稳。缺点是配置是IDE级的不会跟着项目走团队成员各自clone项目后依然需要网络。所以我个人的习惯是本地临时排查可以用local distribution但项目仓库里永远维护一份正确的wrapper配置以保证新clone项目的人能自动拿到一致版本的Gradle。4.4 重下之后还有两个连带版本要核对重新下载Gradle成功后很多人会马上遇到新的报错。最常见的是Minimum supported Gradle version is X.Y.Z. Current version is X.Y.Z.含义很明确你新下的Gradle版本不在AGP要求的范围内。要么把wrapper改成AGP要求的最低版本及以上要么把AGP降回兼容的旧版本。改完版本后前面4.2或4.3的操作需要重新走一遍。另一个连带问题是JDK。Gradle版本越新对JDK版本要求越高。比如AGP 8.x要求Gradle自带或配JDK 17如果你在Gradle JDK设置里还选着JDK 8就算Gradle发行版下载完整、依赖坐标都正确Sync照样会失败报错形式可能是Unsupported class file major version这种晦涩提示。所以每次动Gradle版本我都有个自觉动作顺手检查AS设置里的Gradle JDK以及项目里是否有JDK版本相关的配置。5. 修完不能白修防止下次再躺枪的长期配置5.1 统一仓库镜像让仓库配置能跑遍全团队依赖解析问题的很多根源是仓库配置分散且不统一。我的建议是全部收敛到settings.gradle里集中管理。一个兼顾国内网络和私有仓库的配置示例如下dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() maven { url https://jitpack.io } maven { url https://mirrors.aliyun.com/repository/central } } }在repositories里加一个阿里云的central镜像主要为了给那些不在Google仓库里的通用Java库加速。google()这行建议保留AGP和AndroidX从官方Google仓库拉取更稳不要把它替换掉。如果是公司有私有Nexus或Artifactory直接加一个maven { url ... }就行需要认证时可以配credentialsmaven { url uri(https://nexus.example.com/repository/maven-public/) credentials { username System.getenv(NEXUS_USER) ?: defaultUser password System.getenv(NEXUS_PASS) ?: defaultPass } }注意不要把明文密码提交到git仓库用环境变量注入是团队协作里更稳妥的做法。这里有个现实提醒镜像只是中间代理它没有的依赖一样会404。如果某些冷门依赖在镜像上没有同步全量你还是得临时把仓库地址换回mavenCentral()。所以镜像配置不是越多越好而是够用就好。5.2 收好这张AGP与Gradle版本匹配表排查依赖问题前先确认AGP和Gradle版本在合理范围内能省掉一大半弯路。这里给一张常用的兼容对照表够平时排查用AGP版本最低Gradle版本建议JDK4.2.26.7.1JDK 8 / 117.07.0.2JDK 117.17.2JDK 117.27.3.3JDK 117.37.4JDK 117.47.5JDK 118.08.0JDK 178.18.0JDK 178.28.2JDK 178.48.6JDK 17精确匹配以Android开发者官网的AGP release notes为准这张表用于快速对照排查够用。特别注意一点不要为了消掉一个编译错误顺手把AGP升到8.x。AGP升上去Gradle、JDK可能都得跟着动Kotlin插件和其他依赖库的兼容性也要重新校验这往往会从一个小问题滚成一个大雪球。改版本之前先问自己一句这个依赖问题的根源真的是AGP版本不够新吗5.3 缓存是双刃剑什么时候该清什么时候该留Gradle缓存机制的本意是加速但有时候它会成为Unable to resolve dependency的元凶。什么时候该清什么时候别清我需要单独说明白。该清的场景升级依赖版本后仍然解析到旧版本报错里出现Checksum mismatch或SHA-256不匹配某个版本因为下载中断留下残缺文件。这种时候果断删掉caches/modules-2/files-2.1里对应的坏条目让Gradle重新下载。不该清的场景团队内网环境或离线环境下所有依赖都已经齐全了。这时候清缓存等于自残因为Gradle发不出网络请求没有缓存的支撑项目甚至同步都同步不起来。这种环境下正确做法是启用离线模式./gradlew assembleDebug --offlineAS里也可以在Settings Build Tools Gradle里勾选Offline work。如果离线模式下构建能成功那就更有把握确认问题出在网络链路而不是仓库配置。还有一个实用经验缓存目录可以在机器间拷贝达到提前把依赖准备好的效果。比如给一台新入职同事的电脑拷贝~/.gradle/caches/modules-2/files-2.1能让对方在弱网环境下也快速跑起项目。但注意跨操作系统拷贝大概率会出现奇怪的IO错误同系统环境下拷贝才有意义另外拷贝缓存之前确认两台机器的Gradle wrapper版本一致否则缓存结构对不上拷贝等于白费。我自己实践下来最稳的用法是只针对性拷贝报错依赖对应那条目录而不是整个caches目录搬家。最后说个我自己的习惯不管是AS里看到的Unable to resolve dependency还是命令行里的一串Could not download动手之前我都会先花两分钟把gradle-wrapper.properties、settings.gradle、AGP版本这三样东西过一遍。原因很简单大部分这类问题要么是网络要么是配置重下Gradle往往只是方案A但要是配置本身不对重下多少次都是白搭。这几年我把这套检查做成肌肉记忆之后项目同步失败的次数明显少了很多。希望这篇记录的过程能帮你少走几步弯路。
返回列表