首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Flutter Android 构建避坑实录(二):心跳会骗人——如何判断构建是活着还是已经僵死

Flutter Android 构建避坑实录(二):心跳会骗人——如何判断构建是活着还是已经僵死

原创
作者头像
用户12714909
发布2026-08-25 18:47:04
发布2026-08-25 18:47:04
30
举报

环境:Windows + 国内网络,Flutter 3.44.x / Gradle 8.10.2。上篇讲锁文件,这篇讲一个更隐蔽的问题:构建进程看着活着,其实早就死了。

一个真实场景

构建脚本后台运行,日志文件持续增长,心跳输出一切正常——你以为它在干活,实际上它已经僵死了一个半小时。这时候你的选择只有两个:继续等(大概率继续白等),或者杀掉重来(之前的时间全沉没)。

区分这两种状态,是构建排障里最值钱的技能。

僵死的完整根因链

以我排查实锤的案例还原一下:

  1. 上一轮构建异常退出,Flutter SDK 的 bin/cache/lockfile 残留
  2. 构建脚本尝试删锁,删除操作被系统的安全机制拦截,但脚本只打了个 warning 就继续跑
  3. Gradle 启动 flutter.bat → flutter.bat 在 acquire_lock 上无限等待
  4. dart.exe 进程从未出现,Gradle 的 java 进程傻等一个不存在的下游进程
  5. 脚本层面的心跳(node 进程还活着)持续输出 → 看起来一切正常

注意第 2 步:"只警告不阻塞"的设计是僵死的帮凶。删锁失败必须当致命错误处理,立即退出并给出手动处理指引。

判断构建死活的三个可靠指标

❌ 不可信指标

  • 脚本心跳输出正常(心跳只证明脚本进程活着,不代表构建链路活着)
  • 日志文件在缓慢增长(心跳写日志也会让文件变大)
  • CPU 有占用(Gradle 傻等时也可能有少量 CPU 活动)

✅ 可信指标

① 进程链完整:构建正常推进时,dart.exe 必然存在(AOT 编译阶段)。用:

代码语言:bash
复制
tasklist | grep -iE "java|dart|gradle"

Gradle 的 java 进程在而 dart.exe 迟迟不出现 = 大概率卡在锁上。

② 产物目录有文件活动

代码语言:bash
复制
find <项目>/build -newermt "10 min ago" -type f | head

10 分钟内 build 目录零新文件,基本可以判死。

③ 任务阶段在推进:日志里的 Gradle task 名(:app:compileXXX)应该不断往前走。停在同一个 task 超过 30 分钟且产物无活动 = 僵死。

经验阈值:30 分钟 build 目录零文件活动,立即处理,不要抱侥幸心理。

"慢"和"死"是两回事

另一个容易误判的点:全量构建到底该多久?

我实测的数据:正常全量构建(clean build)15 分钟左右。但有一种情况会到 40-70 分钟也属正常——全新的 GRADLE_USER_HOME 首轮构建,配置阶段要做几千个依赖的 checksum 校验和远程 HEAD 探测,光这就能花掉 20 多分钟。

区分方法还是上面三条:慢 = 日志持续推进 + task 名不断变化 + 最终成功;死 = 停在同一个地方 + 产物零活动。日志在动就是真干活,别急着杀。

"泊车"策略:删不起,就改名

常规思路是构建前把旧的缓存/构建目录 rm -rf 掉。但如前文所说,旧文件的删除在 Windows 上经常被安全机制拦截(删新文件没事,删旧文件被拦)。怎么办?

用 rename 代替 delete

代码语言:javascript
复制
// 把旧目录改名"泊车",让本轮构建在全新路径上跑
fs.renameSync(oldDir, oldDir + "_stale_" + Date.now());

改名(rename)操作不走删除通道,基本不会被拦。构建永远在全新目录上进行,旧目录留着事后统一清理。

这个策略还有个附加收益:每轮都是 clean build,产物干净可复现。代价是没有增量编译、每轮全量 AOT 编译——但在文件写入随时可能被拦的环境里,全量重建反而是唯一稳定解。

泊车的副作用:stale 目录是磁盘杀手

每轮泊车都留一个 *_stale_* 目录,脚本如果从不清理,用不了多久就是几百个目录、几个 GB 的垃圾。实测一个项目一个月攒了 278 个 stale 目录 + 4.7GB build 垃圾

泊车策略必须配套清理机制:每轮构建前先扫一遍 *_stale_* / *_bak_*,超过 N 天或总量超限就批量清掉。

总结:一份僵死处置 SOP

代码语言:txt
复制
  1. 发现 30 分钟零产物活动 → 判定僵死
  2. 杀干净所有 java/dart/node 相关进程(注意 Windows 下 taskkill 在某些 shell 里参数解析有坑,用 PowerShell 的 Stop-Process 更稳) 1. 手动删三类锁文件(见上篇清单) 2. 确认无孤儿进程 3. 重启构建,盯前 2 分钟日志确认进入编译阶段

两篇加起来就是一套完整的"构建环境止血方案"。第三篇讲讲这些坑是怎么被系统性沉淀下来的——工具链之外的工程方法论。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一个真实场景
  • 僵死的完整根因链
  • 判断构建死活的三个可靠指标
    • ❌ 不可信指标
    • ✅ 可信指标
  • "慢"和"死"是两回事
  • "泊车"策略:删不起,就改名
  • 泊车的副作用:stale 目录是磁盘杀手
  • 总结:一份僵死处置 SOP
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档