Gradle任务被标记为“UP-TO-DATE”失效的根本原因是输入/输出声明不完整或不准确,导致缓存机制无法正确判断复用条件;必须显式声明inputs/outputs并启用@CacheableTask,否则默认不缓存。

Gradle 任务为什么总被标记为“UP-TO-DATE”失效
根本原因不是任务逻辑写错了,而是 Gradle 的输入/输出声明不完整或不准确,导致它无法正确判断「上次构建结果是否还能复用」。只要 inputs 或 outputs 有遗漏、路径动态生成未声明、或文件内容实际变了但时间戳没更新,Gradle 就会放弃缓存,强制重新执行。
-
inputs.files漏掉了参与编译的配置文件(比如buildConfigFields对应的 JSON、模板文件) -
outputs.dir指向了子目录,但实际产物写到了更深层(Gradle 只监控声明路径下的直接变更) - 任务用了
project.fileTree动态扫描资源,但没通过inputs.files显式注册——Gradle 不会自动跟踪这种隐式读取 - 构建中调用了外部命令(如
exec),但没声明其输入文件或环境变量依赖,Gradle 认为「无输入 → 总需重跑」
用 --scan 和 --info 快速揪出具体哪个输入触发了重执行
运行带诊断参数的构建命令,比翻 build.gradle 更快定位问题点:
- 执行
./gradlew assembleDebug --scan,打开生成的 Build Scan 链接,在「Task Execution」页筛选目标任务,点开「Inputs & Outputs」看哪些文件的hash或lastModified发生了变化 - 执行
./gradlew assembleDebug --info,搜索关键词file has changed或input file timestamp changed,日志里会明确指出是哪个路径触发了脏检查 - 对自定义任务,加一句
doFirst { logger.lifecycle("Inputs: ${inputs.files.files} | Outputs: ${outputs.files.files}") },把实际参与比较的路径打出来核对
自定义任务必须显式声明 inputs/outputs 才能被缓存
很多人以为只要任务没报错,Gradle 就会自动缓存。其实默认情况下,所有自定义任务都是 @CacheableTask 关闭状态,连基础缓存都不启用。
- 必须加上
@CacheableTask注解(Kotlin DSL)或cacheable = true(Groovy DSL) -
inputs至少要覆盖:源码路径、配置文件、生成代码的模板、影响行为的系统属性(如project.findProperty("env")) -
outputs必须是最终产物路径,不能只写父目录;若产物名含时间戳或哈希,得用outputs.upToDateWhen { false }显式禁用缓存 - 避免在
doLast里写文件——Gradle 只校验outputs声明路径,不追踪运行时任意 IO
AGP 8.0+ 下 Android 相关任务容易误判的坑
Android Gradle Plugin 升级后,很多内置任务(如 mergeDebugResources、compileDebugJavaWithJavac)改用 Lazy Configuration,但部分插件或旧版自定义逻辑仍用传统方式读取 project.buildDir,造成输入路径不一致。
- 检查是否在
build.gradle里手动设置了buildDir = "build-${version}"—— 这会让所有输出路径失效,每次构建都算「全新」 - 确认没在
android.applicationVariants.all里动态修改variant.outputs,这类操作会绕过 AGP 的增量计算机制 - 如果用了
sourceSets.debug.resources.srcDirs动态添加路径,必须同步调用inputs.files注册,否则 Gradle 看不到这些新增源
真正卡住构建速度的,往往不是任务本身慢,而是 Gradle 反复决定「这个任务不能跳过」。输入输出声明不是可选装饰,而是缓存生效的开关。漏掉一个配置文件路径,整个任务链就退化成全量构建。











