gradle任务在配置阶段即被创建是因其需构建完整任务图以确定执行顺序;应优先使用tasks.register()延迟实例化,避免在配置闭包中执行i/o或状态检查,而应移入dofirst/dolast。

为什么 task 在配置阶段就被创建了
Gradle 的任务对象在配置阶段(configuration phase)就完成实例化和基本配置,哪怕你根本没运行它。这是设计使然:Gradle 需要先构建完整的任务图(task graph),才能决定哪些任务该执行、按什么顺序执行。所以只要你在 build.gradle 里写了 task myTask 或通过插件注册了任务,它就会被立即创建——哪怕后续 never 被调用。
用 register() 替代 create() 来延迟任务实例化
Gradle 5.0+ 推荐使用 tasks.register() 而不是 tasks.create()。前者只注册任务模板,不创建实际 Task 对象;后者直接 new 出 Task 实例,触发完整配置逻辑,容易引发副作用。
-
tasks.create('myTask'):立刻构造 Task 实例,执行所有闭包内代码(包括可能抛异常、读文件、访问未就绪的 extension 属性) -
tasks.register('myTask', MyTaskClass):仅声明任务存在,真正实例化推迟到执行阶段或显式调用get()时 - 如果你在
register的配置闭包里访问了尚未初始化的project.version或自定义 extension,仍会报错——因为闭包会在注册时求值;此时应改用configureEach或把逻辑移到doFirst
避免在 task 配置块里做 I/O 或状态检查
常见错误是把路径存在性判断、环境变量读取、远程配置拉取等逻辑写在 task { ... } 顶层,导致每次配置都执行。这类操作必须收进 doFirst {} 或 doLast {},否则不仅提前创建任务,还可能阻塞整个构建流程。
- 错误写法:
if (!file.exists()) { throw new GradleException(...) }写在 task 块外层 → 配置阶段就失败 - 正确写法:
doFirst { if (!file.exists()) { throw new GradleException(...) } }→ 仅当该 task 真正被执行时才校验 - 注意:
@InputFile、@OutputDirectory等注解声明的属性,Gradle 会在执行前自动校验,无需手动写 exists() 判断
插件中注册任务也要守这个规则
自定义插件里如果用 project.tasks.create() 注册任务,等于把问题打包进插件——所有应用该插件的项目都会在配置阶段强创建任务。应该统一改用 project.tasks.register(),并在 configureEach 或任务自身的 configure 闭包中谨慎控制副作用范围。
尤其要注意 Android 插件等第三方插件可能已用 create() 注册了大量任务,如果你的插件又依赖它们(比如 dependsOn 'preBuild'),就间接触发了那些任务的提前创建。这时可考虑用 tasks.named('preBuild').configure { ... } 替代直接引用,进一步延迟绑定。











