一、Gradle的两个阶段是什么?

想象你是个导演,正在拍一部电影。Gradle构建过程就像你的拍摄流程,分为两个关键阶段:准备阶段(配置阶段)和实际拍摄阶段(执行阶段)。配置阶段相当于选演员、搭场景,执行阶段才是真正开机拍戏。

举个例子,当你运行gradle build时:

// 技术栈:Gradle构建脚本
// 这是一个简单的build.gradle文件示例
task compileJava {
    doLast {
        println '正在编译Java代码...' // 执行阶段才会运行
    }
}

println '正在配置构建脚本...' // 配置阶段就会立即执行

运行后会先输出"正在配置构建脚本...",只有当你真正执行任务时才会看到编译信息。这就是典型的配置阶段和执行阶段的区别。

二、为什么配置阶段容易踩坑?

很多开发者会误以为写在任务外部的代码是"初始化代码",其实它们会在配置阶段立即执行。常见陷阱包括:

  1. 盲目执行耗时操作
// 错误示范:在配置阶段读取大文件
def configFile = new File('huge-config.json').text // 立即加载!

task processData {
    doLast {
        // 实际处理数据的代码...
    }
}
  1. 误用动态属性
// 错误示范:尝试在配置阶段访问未初始化的属性
task myTask {
    def result = expensiveCalculation() // 配置阶段计算
    doLast {
        println result // 可能不是最新值
    }
}

正确的做法应该是把这类操作放到执行阶段:

task smartTask {
    doFirst { // 执行阶段开始时的操作
        def dynamicValue = calculateWhenNeeded()
    }
    doLast {
        // 实际任务逻辑
    }
}

三、高级技巧:控制配置时机

Gradle提供了几种精细控制配置的方式:

  1. 惰性配置:使用Provider API
// 使用Provider延迟计算
def messageProvider = providers.provider {
    "当前时间:${new Date()}" // 只在需要时计算
}

task showTime {
    doLast {
        println messageProvider.get() // 执行时才获取值
    }
}
  1. 条件化配置
// 只在特定条件下配置任务
if (project.hasProperty('enableFeature')) {
    task featureTask {
        doLast {
            println '高级功能已启用'
        }
    }
}
  1. 避免配置循环
// 错误示范:循环依赖导致无限配置
task taskA {
    dependsOn taskB
}
task taskB {
    dependsOn taskA // 会导致配置死循环
}

四、实战:优化构建脚本

让我们看一个真实的构建脚本优化案例。优化前:

// 旧版脚本:配置阶段做了太多工作
def serverIp = fetchFromDatabase() // 配置阶段就查询数据库

task deploy {
    doLast {
        println "部署到${serverIp}" // 可能拿到过时IP
    }
}

优化后版本:

// 新版脚本:按需获取数据
task deploy {
    doFirst {
        def currentIp = fetchFromDatabase() // 执行阶段才查询
        println "准备部署到${currentIp}"
    }
    doLast {
        // 实际部署逻辑
    }
}

五、特殊场景处理技巧

  1. 多项目构建
// settings.gradle中控制子项目配置
include 'mobile', 'web'
project(':mobile').projectDir = file('android') // 配置阶段设置

// 子项目只有在被需要时才会完全配置
configure(subprojects.findAll { it.name == 'web' }) {
    // 只配置web子项目
}
  1. 插件开发注意事项
// 自定义插件示例
class MyPlugin implements Plugin<Project> {
    void apply(Project project) {
        // 这里的代码在配置阶段执行
        project.task('customTask') {
            doLast {
                // 实际逻辑
            }
        }
    }
}

六、性能优化建议

  1. 使用配置缓存
# 命令行启用配置缓存
gradle build --configuration-cache
  1. 避免配置阶段IO操作
// 错误示范
task badTask {
    def files = fileTree('src').files // 配置阶段扫描文件系统
    doLast {
        // ...
    }
}

// 正确做法
task goodTask {
    inputs.dir('src') // 声明输入而不立即读取
    doLast {
        def files = fileTree('src').files // 执行阶段才读取
    }
}

七、常见问题解答

Q:如何判断代码在哪个阶段执行? A:最简单的测试方法:

println '这段代码在配置阶段输出'
task testPhase {
    doFirst {
        println '这段代码在执行阶段输出'
    }
}

Q:为什么我的任务总是执行? A:可能是因为在配置阶段修改了输入输出:

// 错误示范
task alwaysRun {
    outputs.file('build/output.txt') // 配置阶段创建文件
    doLast {
        // 任务永远被认为需要执行
    }
}

八、总结与最佳实践

理解Gradle两阶段的核心要点:

  1. 配置阶段是"准备食材",执行阶段是"炒菜"
  2. 任务配置代码会立即执行,任务动作代码延迟执行
  3. 耗时操作应该尽量放到执行阶段

推荐做法清单: ✓ 使用doLast/doFirst包裹核心逻辑 ✓ 复杂计算使用Provider延迟加载 ✓ 避免在配置阶段进行IO操作 ✓ 多项目构建使用条件化配置 ✓ 启用配置缓存提升性能

记住这些原则,你的构建脚本将会更加高效可靠!