多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Android项目Gradle下载慢?镜像加速配置与构建提速全攻略

Android项目Gradle下载慢?镜像加速配置与构建提速全攻略 新建安卓项目最让人难受的不是业务代码而是第一次同步时卡在Gradle下载上。新工程刚创建完Android Studio就开始后台解析界面经常卡在“Downloading gradle-8.x-bin.zip”等半天进度条不动好不容易发行版下完依赖仓库又开始慢慢拉第一次构建等十分钟是常有的事。这次我想把安卓app初始配置阶段怎么解决Gradle下载慢、怎么设置镜像下载地址这件事从头到尾梳理一遍。这个问题的本质其实就两层第一层是Gradle发行版本身要下载第二层是依赖仓库里的插件和第三方库要下载。很多人只会改其中一个结果发现还是慢。下面我会从问题拆解、镜像选型、实际配置、问题排查以及额外加速手段几个方面展开覆盖新工程、老工程和团队初始化模板这几种场景。不管你是刚接触安卓的新手还是接手别人项目的老手这套配置思路都适用。1. 项目初始阶段的下载慢问题先搞明白到底慢在哪1.1 卡在Gradle发行版下载每个安卓工程根目录下都有一个gradle/wrapper/gradle-wrapper.properties文件里面指定了项目要使用的Gradle版本。第一次执行同步时Gradle Wrapper会去services.gradle.org下载对应版本的Gradle压缩包。这个包一般一百多MB如果网络环境到官方服务器延迟比较高下载过程很容易超时重试。更要命的是Gradle版本不固定时同一个开发机可能要下载多个不同版本的Gradle。新建项目默认用一个版本接手老项目又可能是另一个版本于是每个人的本机上都攒了一堆缓存压缩包。所以新工程初始配置的第一步就应该把这个发行版下载地址替换成可用的镜像地址否则每次在下载环节都会浪费大量时间。顺便说一句Android Studio自己内置的Gradle和Wrapper指定的Gradle不一定一致。如果工程里配置的是Wrapper模式实际构建就会按distributionUrl去下载。因此不要以为Android Studio能打开工程就等于Gradle已经准备好了很多首次同步的卡顿都发生在这一步。1.2 依赖仓库下载慢发行版下完之后Gradle还需要从远程仓库解析安卓构建插件、Kotlin插件以及各种第三方依赖。默认仓库主要是google()和mavenCentral()再加上Gradle插件仓库。这些仓库的服务器同样在公网跨区域访问时下载速度不稳定尤其是依赖数量一多每个库都要等HTTP响应整体构建时间会被拖得很长。新项目的依赖解析顺序通常是先加载Android Gradle Plugin再加载项目里声明的各种库。这两个阶段都依赖远程仓库。如果仓库地址不优化哪怕Gradle发行版本地已经有了同步依旧可能卡上几分钟。所以镜像配置要同时覆盖插件管理和依赖解析两部分缺一不可。1.3 为什么初始配置阶段就要动手很多人的习惯是先跑一次默认的同步等它失败了或者实在慢得受不了了才想起来改镜像。这样做的代价是Gradle可能已经建了一部分缓存目录甚至因为下载中断留下损坏的文件后续清理起来更麻烦。更合理的做法是新建工程之后、第一次同步之前就把gradle-wrapper.properties和settings.gradle.kts里的仓库地址改好让所有下载都直接走镜像。初始配置阶段改东西最少项目结构最干净也没有历史构建产物需要处理。这时候动手相当于把网络层问题提前解决掉后面再依赖大量第三方库时下载速度才有基础保障。2. 镜像加速核心思路到底应该替换哪些下载地址2.1 Gradle发行版镜像和Maven镜像要分开看Gradle下载慢涉及两类不同的下载动作需要的镜像地址也不一样不能混为一谈。我见过有人只改了Maven仓库镜像结果Gradle发行版还是去官方地址下载也见过有人只改了distributionUrl结果依赖解析还是慢。下表可以很直观地看出两者的区别下载类型默认地址常见问题镜像建议Gradle发行版services.gradle.org包体大跨区域下载慢云平台的gradle发行版镜像目录Maven依赖仓库google()、mavenCentral()请求数量多同步超时公共Maven镜像仓库Gradle插件仓库plugins.gradle.org等插件版本多解析慢Maven镜像中的gradle-plugin仓库在配置时要形成条件反射看到distributionUrl就想到发行版镜像看到repositories就想到Maven镜像。两条线都打通构建速度才算真正救回来。2.2 公共镜像怎么选可用性、同步速度和稳定性公共镜像站的地址比较多我常用的有三类一是云厂商提供的Gradle发行版目录二是Maven公共仓库三是针对Gradle插件的专用镜像仓库。选的时候不用追求“最新最全”稳定和能用更重要。以公开的镜像地址为例Gradle发行版可以用类似https://mirrors.cloud.tencent.com/gradle/gradle-8.10.2-bin.zip的地址也可以从华为云镜像目录里找对应版本。Maven仓库方面比较通用的做法是使用https://maven.aliyun.com/repository/public、https://maven.aliyun.com/repository/google和https://maven.aliyun.com/repository/gradle-plugin这三组地址分别覆盖公共依赖、Google仓库依赖和Gradle插件。依赖仓库镜像最有价值的一点是它把多个远程仓库的请求集中到一个访问速度更稳定的入口。比如public仓库本身聚合了Maven Central和JCenter等老地址的历史构件很多旧项目直接改用这一个镜像就能解决大部分同步问题。不过要注意镜像仓库不是百分百完整同步遇到版本过新或者冷门依赖找不到时还得回退到官方仓库所以配置顺序不能乱。2.3 仓库优先级设计镜像放前官方仓库保底Gradle查找依赖时会按照仓库声明的顺序逐个访问一旦找到就会停止搜索。因此镜像应该放在官方仓库前面这样每次解析依赖都优先请求镜像地址速度会快很多同时官方仓库不能简单删掉否则镜像没有同步的依赖会直接报错。有些团队为了追求“绝对不访问官方仓库”把所有官方仓库都移除结果某个库在镜像上短暂缺失构建立刻失败。正确姿势是镜像仓库在前、官方仓库在后Gradle先问镜像找不到再问官方。这个顺序可以用下面这个思路理解优先去离你近的仓库拿东西拿不到再去原来的仓库兜底而不是直接把原来的仓库封死。如果团队成员还有内网私有仓库比如自建的构件管理服务那么私有仓库通常应该放在最前面原因是内部依赖只有私服才有镜像和官方仓库都找不到。初始配置阶段把私有仓库、镜像仓库、官方仓库的顺序一次性理清后面几乎不用再动。3. 实操落地初始化配置里的三处镜像地址3.1 改wrapper先从Gradle发行版下手打开新工程根目录下的gradle/wrapper/gradle-wrapper.properties核心内容是一行distributionUrl。默认是这样的distributionBaseGRADLE_USER_HOME distributionPathwrapper/dists distributionUrlhttps\://services.gradle.org/distributions/gradle-8.10.2-bin.zip networkTimeout10000 validateDistributionUrltrue zipStoreBaseGRADLE_USER_HOME zipStorePathwrapper/dists把distributionUrl换成镜像地址即可比如distributionUrlhttps\://mirrors.cloud.tencent.com/gradle/gradle-8.10.2-bin.zip注意版本号要跟原来保持一致。不要只改域名而忽略版本不同版本号对应不同的构建特性和插件兼容要求。如果不想用腾讯云镜像也可以去其他公开镜像站点找同名文件。我通常还会把networkTimeout从默认值稍微调大一点比如改成30000避免镜像偶尔响应偏慢导致下载被判定超时。改完之后不要直接手动解压或者复制到Gradle目录让Wrapper自己通过镜像地址下载最干净。因为Wrapper会把文件放到独立的缓存目录并做校验手动放文件常常放错位置。3.2 改settings新版工程把依赖仓库收敛到镜像新版Android工程使用settings.gradle.kts统一管理插件和依赖仓库。初始配置需要同时修改pluginManagement和dependencyResolutionManagement两段。以Kotlin DSL为例完整写法如下pluginManagement { repositories { maven(url https://maven.aliyun.com/repository/gradle-plugin) maven(url https://maven.aliyun.com/repository/google) maven(url https://maven.aliyun.com/repository/public) google() mavenCentral() } } dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { maven(url https://maven.aliyun.com/repository/google) maven(url https://maven.aliyun.com/repository/public) google() mavenCentral() } }如果工程使用的是Groovy DSL也就是传统settings.gradle对应写法是pluginManagement { repositories { maven { url https://maven.aliyun.com/repository/gradle-plugin } maven { url https://maven.aliyun.com/repository/google } maven { url https://maven.aliyun.com/repository/public } google() mavenCentral() } } dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { maven { url https://maven.aliyun.com/repository/google } maven { url https://maven.aliyun.com/repository/public } google() mavenCentral() } }这里有两个容易忽略的点。第一个是repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)它的意思是强制项目模块的build.gradle里不要再单独声明仓库所有依赖统一走settings.gradle里配置的这一份。新工程默认会带上这行老工程迁移时如果模块里有repositories同步阶段会直接报错需要把模块里的仓库声明清理掉。第二个点是google()和mavenCentral()留在最后作用就是兜底。面对老项目时根目录build.gradle里可能还有一段buildscript和allprojects也需要同步替换。常见写法是buildscript { repositories { maven { url https://maven.aliyun.com/repository/google } maven { url https://maven.aliyun.com/repository/public } maven { url https://maven.aliyun.com/repository/gradle-plugin } } } allprojects { repositories { maven { url https://maven.aliyun.com/repository/google } maven { url https://maven.aliyun.com/repository/public } } }如果你的老工程中allprojects里已经声明了仓库而同时settings.gradle又开了FAIL_ON_PROJECT_REPOS两处会打架。老工程迁移建议先遵循原有结构去改不要机械地照搬新工程模板。3.3 全局init脚本适合团队模板和CI的进阶方案上面改settings.gradle的方式是随项目走的优点是每个开发者的工程配置一致缺点是一个一个项目改太重复。如果希望本机所有Gradle工程都自动走镜像可以在用户目录下的~/.gradle/init.d/里放一个init.gradle脚本。脚本内容可以写成这样allprojects { repositories { maven { url https://maven.aliyun.com/repository/google } maven { url https://maven.aliyun.com/repository/public } } } settingsEvaluated { settings - settings.pluginManagement.repositories { maven { url https://maven.aliyun.com/repository/gradle-plugin } maven { url https://maven.aliyun.com/repository/google } maven { url https://maven.aliyun.com/repository/public } } }这个脚本会对本机所有Gradle项目生效包括不是安卓的Java项目。它的好处是初始化一次之后新建工程只要不改settings.gradle也能直接享受镜像加速坏处是如果以后想临时调试官方仓库的问题会多一层干扰。团队统一使用CI构建时也可以在CI运行用户目录下放置同样的init.gradle让每次构建都从镜像拉取。不过init.d脚本不是项目内的标准配置新成员如果不知道这回事可能会疑惑为什么自己机器上构建行为跟项目配置不一样。所以我更建议把它当作个人开发机的“基础网络优化”项目级的镜像地址还是写到settings.gradle里更稳妥。4. 改完镜像之后的常见问题与排查技巧实录4.1 明明改了镜像还是从旧地址下载这种情况多半不是配置本身有问题而是实际执行构建时没有走你改的那个配置。我遇到过几种典型情况第一种是改了settings.gradle但gradle-wrapper.properties里的发行版地址没改。此时Gradle发行版依然从官方地址下载你看到的进度条还是老的下载源。第二种是开发机上的Android Studio设置成了“本地Gradle发行版”没有使用Wrapper模式。这种情况下项目里改distributionUrl根本不起作用因为IDE根本没有读取Wrapper配置。第三种是项目里存在多个settings.gradle文件比如根目录和子目录各自有配置Gradle实际加载的是根目录那份改错了位置自然无效。排查时可以执行一次构建并开启更多日志比如在命令行里输入./gradlew help --info日志里能看到Gradle实际解析仓库的顺序和使用的URL。如果显示的地址还是官方地址说明配置没生效优先检查上述三种情况。4.2 依赖在镜像仓库里找不到报错又冒出来了镜像仓库的同步存在一定延迟新发布的依赖版本或者比较冷门的构件在镜像上没有是正常现象。一个常见报错是Could not find com.android.tools.build:gradle:8.x.x看到这个报错先别急着删镜像正确的排查步骤是先确认镜像仓库里到底有没有这个版本的目录。打开镜像站点的目录页面手动走一遍路径看看能不能找到对应版本的目录和POM文件。如果找不到说明镜像还没同步或者该构件根本不在这个镜像范围这时候保留官方仓库作为兜底是必要的。另外要检查一下仓库顺序。如果镜像排在官方仓库之前而镜像上恰好有依赖的某个旧版本Gradle会先拿到旧版本不会继续往后找。遇到“版本不对”的诡异问题可以看看项目里是否依赖了没指定版本的传递依赖或者有没有缓存了旧的解析结果。临时清一次依赖缓存再重新同步往往就能定位。4.3 发行版镜像下载后校验失败或者一直转圈把distributionUrl改成镜像后有时会报校验失败或者连接超时。Gradle发行版镜像通常跟官方文件一致但偶尔因为缓存原因或者镜像临时抽风会碰到损坏文件。此时最简单的方法是先手工用浏览器下载同一个镜像地址的zip文件解开确认大小和内容没有问题再重新触发同步。如果镜像文件没问题但还是报校验错误可以留意gradle-wrapper.properties里是否配置了distributionSha256Sum。如果配置了固定的哈希值而镜像给出的包和官方包在压缩级别上有差异就会校验失败。这种时候应该以官方公布的哈希值为准不要为了强行过校验而随意清空哈希校验。下载进度一直转圈大概率是网络连接没有及时返回而不是配置写错。可以把networkTimeout调大或者临时用命令行工具测试镜像地址的可达性。尽量不要用删除整个.gradle缓存目录的方式去“重来”那样会把已经下载好的其他版本的发行版也一起清掉代价很大。真想清理只删除~/.gradle/wrapper/dists里对应版本号的目录就够了。4.4 常见问题速查现象可能原因处理方向发行版还是从官方地址下载IDE没走Wrapper模式或distributionUrl没改检查Gradle设置和wrapper文件Maven依赖仍然很慢只改了发行版镜像依赖仓库没改检查settings.gradle里的repositories新版本插件找不到镜像同步滞后或插件不在镜像范围保留官方仓库兜底确认版本号校验失败distributionSha256Sum与镜像包不一致核对官方哈希值再配同步报仓库冲突FAIL_ON_PROJECT_REPOS和模块仓库重复清理模块级repositories声明下载中断后重复下载缓存文件损坏只删wrapper/dists对应目录5. 除了镜像初始化构建提速的补充做法5.1 gradle.properties里的构建参数优化镜像解决的是下载速度问题构建速度还受Gradle自身运行参数影响。初始配置时顺手调一下根目录下的gradle.properties收益非常稳定。常见配置如下org.gradle.daemontrue org.gradle.paralleltrue org.gradle.cachingtrue org.gradle.jvmargs-Xmx4096m -Dfile.encodingUTF-8org.gradle.daemontrue是让Gradle后台进程常驻避免每次构建都重新启动虚拟机org.gradle.paralleltrue让多个模块并行构建org.gradle.cachingtrue开启构建缓存相同输入的任务可以直接从缓存拿结果。内存参数可以按机器配置调整新工程建议先设4GB具体看本机可用内存。初始同步阶段这些参数未必能带来肉眼可见的提升但等到项目真正膨胀起来它们的作用会越来越明显。镜像解决外网访问问题参数解决本地执行效率问题两者配合才完整。5.2 团队模板把镜像地址做成默认很多公司的新项目都来自同一个模板仓库如果模板里没写镜像配置每个新成员都会重复踩一遍下载慢的坑。我个人的习惯是在项目模板的settings.gradle.kts里直接内置镜像仓库同时在README里用一小段说明“为什么官方仓库要放在最后”。这样做的好处是任何一个新工程天然就有正确的仓库配置不会出现有人改了、有人没改的情况。团队规模再大一点可以在初始化脚本里统一读取环境变量或者配置文件把镜像地址集中管理起来。这样镜像如果调整了域名只需要改一处配置。但要注意不要在小规模项目里过度设计几个人的小团队直接写在settings.gradle里最简单。5.3 离线模式与私有仓库的延伸如果团队内网自建了构件仓库那镜像和官方仓库都只是外网加速内网仓库才是真正的稳定方案。自建仓库可以代理官方仓库和镜像仓库团队成员统一指向内网地址构建过程中完全不再直接访问外网速度和质量都更有保证。这种方案比较适合持续集成频繁触发的团队初期配置成本会高一些但长期看很划算。如果只是个人机器上网络波动大依赖已经全部下载过还可以临时使用Gradle的离线模式构建。在命令行加上--offline参数Gradle会只使用本地缓存的依赖不发起远程请求。首次构建没有缓存时不能这么用但依赖下全之后再配合离线模式基本能保证任何时间都能快速构建。我个人现在新建安卓项目第一件事不是写代码而是打开gradle-wrapper.properties和settings.gradle.kts把发行版镜像和依赖仓库镜像配好再顺手加上构建参数。这三分钟花下去后面省下的全是等待时间。镜像地址这东西并不复杂难点从来都在“改对了地方”和“出问题时知道往哪排查”。希望这份初始配置的镜像方案能让你的下一个安卓项目少受点折磨。
返回列表