Flutter Android构建卡在Gradle?从依赖下载到NDK的排查指南

发布时间:2026/10/13 13:19:11
Flutter Android构建卡在Gradle?从依赖下载到NDK的排查指南
搞 Flutter Android 的兄弟应该都见过这种画面代码明明没毛病flutter run一敲下去终端日志就停在某一行不动了进度条跟被焊死似的。等五分钟它可能动一下等半小时它可能还在原地。前两天群里一位同学发了张截图标题就是“Flutter Android项目到这一步就卡住了”卡的位置正好是Running Gradle task assembleDebug。这不是个例我早期做 Flutter 开发时也反复踩过后来慢慢整理出一套从定位到修复的排查流程。这篇就按这套流程展开讲目标是以后你再遇到卡住能在十分钟内判断出问题在哪、该怎么处理而不是靠重装清缓存碰运气。1. 先别乱按重启把“卡住”拆成三段再下结论1.1 flutter run 之后的完整执行链条很多人一看到“卡住”就习惯性关掉终端重来这其实是最亏的操作。flutter run背后是一条很长的链路绝不是“编译一下装到手机”这么简单。按顺序大致是这样flutter pub get拉取 Dart 包依赖如果依赖版本校验或缓存出问题这里就可能开始磨蹭。Gradle Wrapper 启动项目里gradle-wrapper.properties会指定一个 Gradle 版本首次运行时需要下载对应发行版几百 MB 的压缩包慢起来像没速度一样。Gradle 配置阶段读取根目录和模块的build.gradle、settings.gradle解析插件和依赖坐标然后把整个依赖图算出来。任务执行阶段这里才是真正的编译、资源合并、DEX 打包最后输出 APK。安装与启动ADB 把 APK 推到设备上如果设备存储慢、调试模式异常也会表现为“卡住”。所以第一步不是猜而是看终端到底停在哪一行。最直接的办法是用详细日志模式flutter run --verbose--verbose会输出非常多的内部日志包括每一阶段开始和结束的时间点。卡在什么时候看最后一行有输出的位置就行。比拿截图去群里问“为什么卡住”要有效得多“日志停在 ... 这一行然后就不动了”这个信息量完全不一样。1.2 三种最典型的“卡住”现场我遇到的卡住大部分可以归成三类用一张表说明现场表现日志关键信息大概率原因优先动作停在Running Gradle task assembleDebug进度条长时间不动CPU 占用很低Gradle 在等待依赖下载、锁文件或网络请求加--verbose看依赖解析是否卡住停在Resolving dependencies或Downloading有网络请求迹象但极慢依赖仓库访问慢、镜像没配好检查仓库镜像配置提前准备 Gradle 发行版停在Waiting for another flutter command...提示等待其他 Flutter 命令释放锁之前的 flutter 进程没退出占用了缓存锁清理残留 dart/flutter 进程删除锁文件这三类占据了九成以上的“卡住”。后面展开讲的排查和修复本质上就是围绕这几类现象在做文章。2. 九成“卡住”的根源都在依赖下载环节2.1 为什么第一次构建会慢到像卡死Flutter Android 项目的依赖是出了名的重。AndroidX、Kotlin 插件、Gradle 插件、第三方 SDK随便一个中等规模项目拉下来的依赖包可能就有上千个。首次构建时Gradle 要把这些坐标全部请求一遍还要下载、校验、缓存。问题在于默认配置里仓库顺序通常是repositories { google() mavenCentral() }在部分网络环境下访问这些官方仓库的速度非常不稳定。表现在外就是日志一直停在“解析 xx 依赖”CPU 占用很低进度条几乎不动。这和我说的“像卡死”完全一致但它又确实还在慢慢爬。怎么区分是“慢”还是“死”很简单等一段时间看日志有没有新增一行。如果十分钟内一点新输出都没有基本可以认定它卡在当前请求上需要干预。不要干等。2.2 仓库镜像把远端仓库换成国内可用地址解决依赖下载慢最核心的操作为设置镜像仓库。常见做法是在项目根settings.gradle新版 Android Gradle Plugin 推荐方式或build.gradle里配置仓库地址。以settings.gradle为例默认长这样pluginManagement { repositories { google() mavenCentral() gradlePluginPortal() } } dependencyResolutionManagement { repositories { google() mavenCentral() } }可以改成类似这样pluginManagement { repositories { maven { url https://你的镜像地址/repository/maven-public } maven { url https://你的镜像地址/repository/google } maven { url https://你的镜像地址/repository/gradle-plugin } gradlePluginPortal() } } dependencyResolutionManagement { repositories { maven { url https://你的镜像地址/repository/maven-public } maven { url https://你的镜像地址/repository/google } mavenCentral() } }注意配置镜像不是把所有官方仓库都删掉。部分依赖可能只在官方仓库里有或者镜像同步不完整。我通常把镜像放前面官方仓库留在最后兜底这样既能提速又不至于遇到“镜像里找不到”的报错。不同云厂商的 Maven 镜像地址不一样具体填哪个去你常用的云厂商公开 Maven 仓库页面看就行。它们一般都提供类似maven-public、google、gradle-plugin的仓库组路径。如果项目里还有自定义私有仓库也可以按同样方式加到列表里。2.3 绕开 Gradle 发行版下载的另一条路依赖下载只是其中一半另一半是 Gradle 发行版本身的下载。打开这个文件android/gradle/wrapper/gradle-wrapper.properties会看到类似distributionUrlhttps\://services.gradle.org/distributions/gradle-8.10-all.zip首次构建时Gradle Wrapper 会把这个 zip 下载到用户目录下的~/.gradle/wrapper/dists/。这个文件往往有 100 MB 以上官方下载地址在部分网络环境下也很慢。提前手动下载并在本地放置文件可以大幅缩短构建等待时间。操作方法不复杂先用浏览器或下载工具把distributionUrl对应的 zip 下载下来。看终端日志或~/.gradle/wrapper/dists/里的目录结构找到对应版本目录。把 zip 放进正确的位置让 Gradle 识别为已下载完成。如果你手边有另外一台能正常构建的机器直接把它~/.gradle/wrapper/dists/下对应内容复制过来也是可行的。这个目录是可以迁移的能省掉新机器首次构建的最大一截等待时间。3. 卡在 assembleDebug 不等于网络问题顺着任务链查3.1 读懂 verbose 日志里的任务链网络问题解决后还有一种常见情况依赖都能下载但还是卡在assembleDebug。这时需要从日志里的任务链入手。--verbose模式下会看到类似 Task :app:compileDebugKotlin Task :app:mergeDebugResources Task :app:dexBuilderDebug Task :app:packageDebug停在哪一行就说明哪一步最耗时。有人可能卡在mergeDebugResources这是资源合并有人卡在compileDebugKotlin这是 Kotlin 编译也有人卡在 NDK 相关任务原因完全不同。看任务链不能只看任务名还要配合资源占用。如果 CPU 很高、Gradle 进程在运行说明它是在拼命计算只是任务重如果 CPU 低到 5% 以下大概率是在等外部资源比如网络、锁、磁盘 IO。3.2 依赖树与冲突不是所有卡住都会报红字依赖冲突是另一类隐蔽的卡住原因。理想情况下两个依赖引用了同一个库的不同版本Gradle 会选择较高版本并结束。但现实里第三方 SDK 经常带着传递依赖版本约束互相矛盾Gradle 会反复尝试解析、重试下载表现出来就是很长一段时间的“静止”。检查依赖树是这一步的关键操作cd android ./gradlew :app:dependencies --configuration debugRuntimeClasspath输出会列出当前模块所有的依赖和版本。看到同一个库出现多个版本比如androidx.core:core:1.9.0和androidx.core:core:1.12.0同时存在就该排查了。进一步用 dependencyInsight 看具体是谁引入的./gradlew :app:dependencyInsight --dependency androidx.core --configuration debugRuntimeClasspath定位到冲突来源后可以显式声明一个统一版本implementation(androidx.core:core:1.12.0)或者用 resolutionStrategy 强制版本configurations.all { resolutionStrategy { force androidx.core:core:1.12.0 } }提示强制版本不是无脑选新版本。要实测目标库 API 是否兼容否则编译过了运行时报NoSuchMethodError那比卡住更难受。3.3 NDK、多 ABI 与第三方 SDK 把编译拖慢如果你项目里接了涉及 C/C 的第三方 SDK比如地图、扫脸、信号处理类的库那么构建时还可能要跑 NDK 和 CMake。这类任务一旦开始会把大量 CPU 和磁盘 IO 都吃掉看起来就像“卡死”。更坑的是Gradle 默认会把多个 CPU 架构都编一遍。打开 APK 分析可以看到lib/armeabi-v7a、lib/arm64-v8a、lib/x86_64等目录每个目录下都有对应的.so文件。调试阶段其实没必要为所有架构编包可以限制defaultConfig { ndk { abiFilters armeabi-v7a, arm64-v8a } }这条配置能显著减少 NDK 任务的编译量。我见过一个项目不加这行时模拟器调试构建要四十多分钟加上之后直接降到十分钟以内。真机调试大多数是 arm64 和 armeabi-v7a保留这两个足够。还要注意首次跑 NDK 任务之前Android SDK 会自动下载 NDK 和 CMake 安装包那也是近 GB 级别的下载同样可能表现为“卡住”。用 Android Studio 的 SDK Manager 提前安装好或者在项目里显式指定一个已存在的 NDK 版本都能避免现场下载。4. 构建像蜗牛一样爬多半是内存和缓存没调好4.1 Gradle JVM 内存不是越大越好但太小必卡依赖和任务都没问题构建还是慢就得看 Gradle 进程本身的运行环境了。Gradle 是一个跑在 JVM 里的构建工具默认内存分配经常不够用。当项目比较大的时候内存不足会触发频繁 GCGradle 看起来像在干活实际上大部分时间都在做内存清理。在android/gradle.properties里可以这样配置org.gradle.jvmargs-Xmx4g -XX:MaxMetaspaceSize1g -XX:HeapDumpOnOutOfMemoryError-Xmx4g是给 Gradle 进程最大 4 GB 堆内存。怎么判断该设多大看机器总内存。8 GB 内存的机器建议 2g 到 3g16 GB 内存可以给 4g再多也未必有提升反而可能挤压系统其他程序。-XX:MaxMetaspaceSize是防止 Kotlin/Groovy 插件编译时元数据区爆掉。另外Android Studio 自己也有独立的内存设置。如果构建时 IDE 也卡打开 Android Studio 的Help Change Memory Settings适当上调 IDE 的堆内存否则 IDE 的索引和构建同时抢内存两边都卡。4.2 并行构建与构建缓存开对了是神器开错了是灾难gradle.properties里还有两个常见开关org.gradle.paralleltrue org.gradle.cachingtrueparallel是让多个模块任务并行执行适合多 Module 项目。但如果机器内存不大并行开起来后多个 Gradle worker 同时吃内存反而容易触发 OOM 或频繁 GC表现为比不开还卡。caching则是构建缓存。它会缓存任务输出只要输入没变化下次构建就能直接复用结果。对经常改同一个模块、App 主体基本不变的项目来说效果极好。我的实际经验是调试阶段优先保证cachingtrueparallel看项目模块数再决定。单模块项目开并行帮助有限多模块项目、机器 16 GB 以上再开并行收益才明显。4.3 别忽略系统层的拖后腿因素有一类“卡住”和项目配置、Gradle 都没关系纯粹是系统环境在拖后腿。最常见的是 Windows 上安全软件对项目目录做实时扫描。Gradle 构建会产生成千上万个临时小文件安全软件逐个扫描IO 就卡住了。排查方法很简单构建时打开任务管理器看磁盘使用率是否长时间处于高位但 CPU 占用又忽高忽低。如果是把项目目录和用户目录下 Gradle 缓存目录加入安全软件的信任列表再重新构建。这一步对不同安全软件的位置不一样但基本都是“设置 排除项”里加目录。还有一个容易被忽略的因素是磁盘剩余空间。Gradle 构建缓存、APK 打包、DEX 编译都会产生大量临时文件磁盘快满时构建速度劣化非常明显。保持系统盘和项目所在盘至少有 20% 以上空余空间算是基本操作。5. 一份可以直接照抄的排查顺序5.1 第一步排查 Flutter 命令锁“卡住”还有一种特殊形态在flutter run之前终端已经卡在Waiting for another flutter command to release the startup lock。这是 Flutter 工具自己在等锁因为之前某个 Flutter 命令没有正常退出。检查方法分平台Windows打开任务管理器找dart.exe或flutter_tools相关的进程确认是否有残留。macOS/Linux执行ps aux | grep dart看看有没有没退干净的后台进程。找到残留进程就先结束它然后删除锁文件。锁文件一般在 Flutter 安装目录的bin/cache/lockfile。删除后重启终端再跑flutter run就能正常拿到锁。这个坑特别容易发生在“上次构建卡死直接关终端”之后。终端被强行关闭Flutter 进程还没有释放锁下次再开项目就会一直等。5.2 第二步清缓存、停 daemon、重新构建确认没有锁文件问题后我习惯用一套“从干净状态出发”的组合命令flutter pub get cd android ./gradlew --stop ./gradlew --status--stop是停掉当前 Gradle daemon--status是查看当前 daemon 状态。停掉 daemon 再构建能让 Gradle 以全新进程加载配置能排除一些“旧 daemon 状态异常”导致的假卡死。接下来是重点先别急着flutter clean。flutter clean会清掉构建产物如果依赖和缓存本来已经下载好了clean 之后反而要重新下一遍。只有在明确需要清理 Gradle 缓存时才用flutter clean flutter pub get顺序不要搞反。flutter clean之后再跑flutter pub get确保依赖配置文件重新生成。5.3 第三步把我的默认 gradle.properties 抄走最后给你一份我日常项目里常用的配置可以直接作为初始模板org.gradle.jvmargs-Xmx4g -XX:MaxMetaspaceSize1g -XX:HeapDumpOnOutOfMemoryError org.gradle.daemontrue org.gradle.paralleltrue org.gradle.cachingtrue android.useAndroidXtrue android.enableJetifiertrue逐行说下org.gradle.jvmargsGradle 进程内存参数16 GB 内存机器推荐 4g8 GB 机器改成-Xmx2g -XX:MaxMetaspaceSize1g。org.gradle.daemontrue让 Gradle 常驻后台进程避免每次构建都重新启动 JVM。默认就是开启的显式写出来方便查。org.gradle.paralleltrue多模块项目建议开单模块项目可选。org.gradle.cachingtrue强烈建议开第二次构建开始能明显感受到差异。android.useAndroidXtrue必须开现在 Flutter 模板默认会有。android.enableJetifiertrue老项目或部分旧第三方库需要新项目如果不涉及 Android Support 库可以不开开着会增加构建开销。如果你的项目里依赖下载还是慢别忘了配合第 2 章的镜像配置一起使用。配置一处不改内存和缓存调得再好下载环节还是会把时间拖回去。6. 现场实录与常见问题速查6.1 三个求助现场三个不同的结局这里用三个最常见的求助场景做一次完整还原全部来自我平时的交流经验。场景一卡在 Resolving dependencies 一个多小时A 同学把项目从 Windows 换到另一台电脑后第一次构建就卡在Resolving dependencies。他试了重装 Flutter、删掉.gradle目录都没用。我看了他的gradle-wrapper.properties版本没问题但默认仓库配置没有镜像。帮他改了settings.gradle里的仓库地址并提前下载了对应 Gradle 发行版 zip 放到wrapper/dists目录再构建时大概两分钟就进入编译阶段了。这类问题基本都是同一个套路首次构建或换机器后的首次构建网络下载环节没解决后面全卡住。场景二卡在 NDK 相关任务以为是死机B 同学的项目集成了一个第三方地图 SDK构建日志一直停在:app:externalNativeBuildCPU 偶尔跳到 100%但任务就是不走。他怀疑是项目有功能问题差点重写。后来检查发现 SDK 默认带了 8 个 ABI 的.soGradle 在为每个架构跑 CMake 编译。在defaultConfig里加上abiFilters只保留armeabi-v7a和arm64-v8a构建时间直接降了两个数量级。NDK 类任务卡住先查 ABI 列表再查 NDK 版本是否匹配基本能定位到根因。场景三卡在 Waiting for another flutter commandC 同学更典型项目没跑起来终端先提示在等另一个 Flutter 命令释放锁。他根本不知道之前有哪个命令没结束重启电脑也没用。其实他上次数次调试时直接点了 IDE 的停止但后台进程没退干净。结束掉残留的dart.exe进程删掉bin/cache/lockfile问题马上消失。6.2 常见问题速查表现象可能原因处理方式预期效果停在Resolving dependencies无动静仓库访问慢或依赖解析重试配置镜像仓库、调整仓库顺序依赖解析时间大幅缩短停在Downloading Gradle无动静Gradle 发行版下载慢手动下载 zip 放入 wrapper dists跳过下载等待直接进入构建停在Running Gradle task assembleDebug任务重、内存不足或锁未释放调 JVM 内存、检查任务链与锁文件构建进度恢复正常推移多 ABI 编译导致反复跑 NDK第三方 SDK 带大量 ABI配置ndk.abiFilters构建时间和体积明显下降提示Waiting for another flutter command上次 Flutter 进程未退出清理残留进程并删除 lockfile立刻拿到启动锁构建中途磁盘 IO 长时间 100%安全软件实时扫描或磁盘空间不足添加信任目录、清理磁盘空间文件读写不再拖慢构建这张表是我处理 Flutter Android 构建卡住问题时最常检索的几个组合。如果你遇到的现象不在表里也别慌回到第 1 章的思路先定位日志卡在哪一行再判断是网络、依赖、内存、资源还是系统干扰。大部分卡住都不是真的有 bug只是某个环节没有为当前环境做好准备。我个人现在每接一个新 Flutter 项目第一件事不是写代码而是先把gradle.properties、仓库镜像、Gradle Wrapper 版本定好再让项目空跑一遍到出 APK。这半小时的初始成本能省掉后面无数个“卡住”的夜晚。最后建议把flutter run --verbose的输出养成习惯有问题就直接把最后几十行日志贴出来信息给得越全别人越好帮你判断。构建卡住的根因大多是依赖与资源问题不是玄学冷静定位十分钟内往往就能解决。