Android Gradle 学习 - 生命周期和Task

前言

上一篇文章Kts Gradle学习 主要是学习新版本的Kotlin Gradle相关配置和以前的Groovy的不同表现方式,这里需要为后边自定义插件编写做准备,所以还是要学习下Gradle的生命周期和一些命令或者Task的基础知识 (ps:最早是20年看的一个大佬的文章入门,时隔多年 重新整理和回顾,在AI的大浪潮下 沉下心稳扎稳打基本功也是可以的)

Gradle生命周期

Gradle 构建分为三个阶段:

初始化阶段 (Initialization)

  • 读取 settings.gradle.kts文件,生成Settings对象,确定哪些项目需要参与构建,为需要构建的项目创建Project对象;

配置阶段 (Configuration)

  • 配置阶段主要是解析build.gradle.kts文件,配置init阶段生成的Project对象,构建根项目和所有子项目,同时生成和配置在build.gradle.kts中定义的Task对象,构造Task的关系依赖图,关系依赖图是一个有向无环图;

执行阶段 (Execution)

  • 根据configure阶段的关系依赖图执行Task.

生命周期钩子实例

可以借助Gradle 留给我们的钩子 ,在 build.gradle.kts 和子中,来打印一下生命周期,熟悉下钩子的使用

kotlin 复制代码
// 配置阶段钩子
beforeEvaluate {
    println("3. 项目配置开始")
}

afterEvaluate {
    println("4. 项目配置完成")
}

gradle.projectsEvaluated {
    println("5. 所有项目配置完成")
}

// 执行阶段钩子
gradle.taskGraph.whenReady {
    println("6. Task 图准备完成")
}

gradle.taskGraph.beforeTask {
    println("7. Task 执行前:$path")
}

gradle.taskGraph.afterTask {
    println("8. Task 执行后:$path")
}

gradle.buildFinished {
    println("9. 构建完成")
}

工程中钩子使用

根目录下的 build.gradle groovy 格式

groovy 复制代码
// 钩子3:每个项目配置开始前
gradle.beforeProject { project ->
    println "\n [配置阶段] ${project.name} 开始配置"
}

// 钩子4:每个项目配置完成后
gradle.afterProject { project ->
    println " [配置阶段] ${project.name} 配置完成"

    // 统计信息
    def taskCount = project.tasks.size()
    def depCount = project.configurations.size()
    println "    Tasks: ${taskCount} | Configurations: ${depCount}"
}

// 钩子5:所有项目配置完成
gradle.projectsEvaluated {
    println "\n [配置阶段] 所有项目配置完成"

    // 统计所有项目的 Task 数量
    def totalTasks = gradle.rootProject.allprojects.sum { it.tasks.size() }
    println "   总 Task 数量:${totalTasks}"
}

// 钩子6:Task 图准备完成(知道要执行哪些 Task)
gradle.taskGraph.whenReady { taskGraph ->
    println "\n [执行阶段] Task 执行计划"

    def tasks = taskGraph.allTasks
    println "将执行 ${tasks.size()} 个 Task:\n"

    // 按项目分组显示
    tasks.groupBy { it.project.name }.each { projectName, projectTasks ->
        println " ${projectName}:"
        projectTasks.each { task ->
            println "   ✓ ${task.name}"
        }
        println ""
    }
}

// 钩子7:每个 Task 执行前
gradle.taskGraph.beforeTask { task ->
    println "  [执行] 开始:${task.path}"
}

// 钩子8:每个 Task 执行后
gradle.taskGraph.afterTask { task ->
    def status = task.state.failure != null ? " 失败" : " 成功"
    def skipped = task.state.skipped ? " (跳过)" : ""
    println "${status} [执行] 完成:${task.path}${skipped}"
}

// 钩子9:构建完成(成功或失败)
gradle.buildFinished { result ->
    if (result.failure != null) {
        println "\n Gradle 构建失败"
        println "\n错误:${result.failure?.message}"
    } else {
        println "\n Gradle 构建成功"
    }
}

Library 模块build.gradle.kts kts格式

kotlin 复制代码
val projectStartTime = System.currentTimeMillis()
println("\n  [${project.name}] 配置阶段开始")

// 钩子:当前项目配置完成
afterEvaluate {
    val configTime = System.currentTimeMillis() - projectStartTime
    println(" [${project.name}] 配置完成,耗时:${configTime}ms")

    // 显示 Android 配置信息
    android.apply {
        println("Android 配置:")
        println("- compileSdk: $compileSdk")
        println("- minSdk: ${defaultConfig.minSdk}")
        println("- targetSdk: ${defaultConfig.targetSdk}")
        println("- versionCode: ${defaultConfig.versionCode}")
        println("- versionName: ${defaultConfig.versionName}")
    }
}

// Task 级别监控(带性能分析)

tasks.configureEach {
    doFirst {
        // 记录每个 Task 的开始时间
        ext.set("taskStartTime", System.currentTimeMillis())
        println("[$path] 任务开始执行...")
    }

    doLast {
        // 计算每个 Task 的耗时
        val start = ext.get("taskStartTime") as Long
        val duration = System.currentTimeMillis() - start

        // 根据耗时分类
        val mm = when {
            duration > 5000 -> "垃圾速度"
            duration > 2000 -> "正常速度" 
            duration > 1000 -> "飞速速度" 
            duration > 0 -> "闪电速度" 
            else -> " ??? "
        }

        println("$mm [$path] 任务完成,耗时:${duration}ms")

        if (duration > 5000) {
            println("警告:任务耗时过长,建议优化!")
        }
    }
}

// 特定 Task 的钩子

// 监控编译 Task
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
    doFirst {
        println("Kotlin 编译开始...")
    }
    doLast {
        println("Kotlin 编译完成")
    }
}

// 监控打包 Task(Library 模块使用 bundleDebugAar)

tasks.matching { it.name == "bundleDebugAar" }.configureEach {
    doFirst {
        println("\n 开始打包 Debug AAR...")
    }
    doLast {
        println(" Debug AAR 打包完成!")

        // 显示 AAR 信息
        val aarDir = File(layout.buildDirectory.asFile.get(), "outputs/aar")
        if (aarDir.exists()) {
            aarDir.listFiles()?.forEach { aar ->
                if (aar.extension == "aar") {
                    val size = aar.length() / 1024.0 / 1024.0
                    println("AAR: ${aar.name}")
                    println("大小: ${"%.2f".format(size)} MB")
                }
            }
        }
    }
}

tasks.matching { it.name == "bundleReleaseAar" }.configureEach {
    doFirst {
        println("\n 开始打包 Release AAR...")
    }
    doLast {
        println(" Release AAR 打包完成!")
        
        // 显示 AAR 信息
        val aarDir = File(layout.buildDirectory.asFile.get(), "outputs/aar")
        if (aarDir.exists()) {
            aarDir.listFiles()?.forEach { aar ->
                if (aar.extension == "aar") {
                    val size = aar.length() / 1024.0 / 1024.0
                    println("AAR: ${aar.name}")
                    println("大小: ${"%.2f".format(size)} MB")
                }
            }
        }
    }
}

查看gradle 运行结果可见

yaml 复制代码
Starting Gradle Daemon...
Gradle Daemon started in 854 ms

> Configure project :
 [配置阶段] GradleY 配置完成
    Tasks: 17 | Configurations: 0

> Configure project :app

 [配置阶段] app 开始配置
 [配置阶段] app 配置完成
    Tasks: 35 | Configurations: 140

> Configure project :GroovyLibrary

 [配置阶段] GroovyLibrary 开始配置
 [配置阶段] GroovyLibrary 配置完成
    Tasks: 33 | Configurations: 80

> Configure project :KtsLibrary

 [配置阶段] KtsLibrary 开始配置

  [KtsLibrary] 配置阶段开始
 [配置阶段] KtsLibrary 配置完成
    Tasks: 34 | Configurations: 80
 [KtsLibrary] 配置完成,耗时:21ms
    Android 配置:
      - compileSdk: 35
      - minSdk: 24
      - targetSdk: null
      - versionCode: null
      - versionName: null

 [配置阶段] 所有项目配置完成
   总 Task 数量:687

 [执行阶段] Task 执行计划
将执行 1 个 Task:

 GradleY:
   ✓ prepareKotlinBuildScriptModel


> Task :prepareKotlinBuildScriptModel UP-TO-DATE
  [执行] 开始::prepareKotlinBuildScriptModel
 成功 [执行] 完成::prepareKotlinBuildScriptModel (跳过)
Download https://maven.aliyun.com/repository/public/javax/inject/javax.inject/1/javax.inject-1-javadoc.jar, took 36 ms

 Gradle 构建成功

BUILD SUCCESSFUL in 24s

如果想查看aar 执行情况可以输出 ./gradlew :KtsLibrary:assemble --no-daemon (gradlew即Gradle Wrapper的简写)

常用命令

下列主要是自己平时使用到的命令,有时候会忘记,这里也捎带上

bash 复制代码
# 查看 Gradle 版本
./gradlew --version

# 查看所有可用 Task
./gradlew tasks

# 查看所有 Task(包括隐藏的)
./gradlew tasks --all

# 查看项目结构
./gradlew projects

# 清理构建产物
./gradlew clean

# 清理并构建
./gradlew clean build

========  Android 构建 ==========
# 构建所有变体
./gradlew assemble

# 构建 Debug 版本
./gradlew assembleDebug

# 构建失败时,查看详细错误
./gradlew assembleDebug --info --stacktrace

# 构建 Release 版本
./gradlew assembleRelease

# 安装 Debug 版本
./gradlew installDebug

# 安装 Release 版本
./gradlew installRelease

# 卸载 Debug 版本
./gradlew uninstallDebug

# 卸载 Release 版本
./gradlew uninstallRelease

===========  查看依赖 ==========
# 查看所有依赖 并输出到文件中
./gradlew app:dependencies > dependencies.txt

# 查看特定配置的依赖
./gradlew :app:dependencies --configuration implementation
./gradlew :app:dependencies --configuration debugRuntimeClasspath

# 查看依赖树,找 (*) 标记
./gradlew :app:dependencies --configuration debugRuntimeClasspath | grep "(*)"

# 查看具体的依赖路径
./gradlew :app:dependencyInsight --dependency okhttp --configuration debugRuntimeClasspath

# 查看任务
./gradlew tasks

还有一些可以自己拼接在后边的 重要参数

gradle 复制代码
# 调试
--info                   # 详细信息
--stacktrace            # 栈追踪
--scan                  # 构建扫描

# 性能
--parallel              # 并行
--build-cache           # 构建缓存
--configuration-cache   # 配置缓存

这里只是简单塞了一些 常用的命令

Task 任务

Task 基本概念

Task(任务)是Gradle中最小的构建单元,但是 Task中又会有很多的Action 是Task中最小的执行单元。 Gradle 构建的核心是由Task组成的有向无环图,所以 各个Task之间是有运行顺序的,不会出现循环依赖等问题。 Task属于 Project对象,所以可以放在根目录或者子目录的 build.gradle.kts文件中。

Task 使用

创建 (推荐使用register 懒加载)

kotlin 复制代码
register(String name, Action<? super Task> configurationAction)
register(String name, Class type, Action<? super T> configurationAction)

// 最简单的Task  不推荐使用 create 直接创建 
tasks.register("hello") {
    doLast {
        println(it.name + " Gradle!")
    }
}

// 带类型的Task 其他的还有 Delete、Zip、Jar等类型
tasks.register<Copy>("copyFiles") {
    from("src/main/resources")
    into("build/resources")
}

// 带配置的Task
tasks.register("printInfo") {
    group = "custom"
    description = "打印项目信息"
    
    doLast {
        println("Project: ${project.name}")
        println("Version: ${project.version}")
    }
}

// 使用案例
./gradlew hello
// 也可以多个Task 执行  (但是Task 的命名是不能重复的)
./gradlew task1 task2 task3

doFirst 和 doLast

kotlin 复制代码
tasks.register("greeting") {
    // doLast:在最后执行
    doLast {
        println("3. Goodbye!")
    }
    
    // doFirst:在最前面执行
    doFirst {
        println("2. Hello!")
    }
    
    // 再添加一个doLast  最后添加的 doLast 最后执行
    doLast {
        println("4. See you!")
    }
    
    // 再添加一个doFirst   最后添加的 doFirst第一个执行
    doFirst {
        println("1. Welcome!")
    }
}

运行结果

markdown 复制代码
> Task :greeting
1. Welcome!
2. Hello!
3. Goodbye!
4. See you!

自定义Task

kotlin 复制代码
abstract class GreetingTask : DefaultTask() {
    
    @Input
    var greeting: String = "Hello"
    
    @Input
    var name: String = "World"
    
    @TaskAction
    fun greet() {
        println("$greeting, $name!")
    }
}

// 使用自定义Task
tasks.register<GreetingTask>("greet") {
    greeting = "你好"
    name = "Gradle"
}

Task 常用属性

kotlin 复制代码
tasks.register("myTask") {
    // 任务名称(只读)
    println("Task name: $name")
    // 任务分组
    group = "custom"
    // 任务描述
    description = "这是一个自定义任务"
    // 是否启用
    enabled = true
    // 超时时间
    timeout.set(Duration.ofMinutes(5))
    // 依赖的Task
    dependsOn("clean")
    doLast {
        println("Executing $name")
    }
}

Task依赖关系

  • dependsOn(强依赖)
kotlin 复制代码
tasks.register("taskA") {
    doLast {
        println("执行 TaskA")
    }
}

tasks.register("taskB") {
    doLast {
        println("执行 TaskB")
    }
}

tasks.register("taskC") {
    // taskC依赖taskA和taskB
    dependsOn("taskA", "taskB")
    
    doLast {
        println("执行 TaskC")
    }
}

执行 ./gradlew taskC: 是必须先执行依赖项的, 有向无环的

arduino 复制代码
> Task :taskA
执行 TaskA

> Task :taskB
执行 TaskB

> Task :taskC
执行 TaskC
  • mustRunAfter (在谁之后 必须的,会报错的那种)
kotlin 复制代码
tasks.register("compile") {
    doLast {
        println("编译代码")
    }
}

tasks.register("test") {
    // 如果test要执行,必须在compile之后
    mustRunAfter("compile")
    
    doLast {
        println("运行测试")
    }
}

执行 ./gradlew compile test

shell 复制代码
> Task :compile
编译代码

> Task :test
运行测试

执行 ./gradlew test compile(顺序相反):

shell 复制代码
> Task :compile  ← 依然先执行compile
编译代码

> Task :test
运行测试

只执行 ./gradlew test

shell 复制代码
> Task :test  ← 只执行test,不会自动执行compile
运行测试
  • shouldRunAfter (在谁之后 不强制,不会报错)
kotlin 复制代码
tasks.register("taskA") {
    doLast {
        println("TaskA")
    }
}

tasks.register("taskB") {
    // 建议在taskA之后执行
    shouldRunAfter("taskA")
    
    doLast {
        println("TaskB")
    }
}
  • finalizedBy (结束后执行 某Task)
kotlin 复制代码
tasks.register("buildApp") {
    // 构建完成后,必须执行清理
    finalizedBy("cleanup")
    
    doLast {
        println("构建应用")
    }
}

tasks.register("cleanup") {
    doLast {
        println("清理临时文件")
    }
}

执行 ./gradlew buildApp

arduino 复制代码
> Task :buildApp
构建应用

> Task :cleanup
清理临时文件

Task跳过

  • onlyIf (条件跳过)
kotlin 复制代码
tasks.register("deployProd") {
    // 只有在生产环境才执行
    onlyIf {
        project.hasProperty("prod")
    }
    
    doLast {
        println("部署到生产环境")
    }
}

执行

bash 复制代码
// 不会执行
./gradlew deployProd

// 会执行
./gradlew deployProd -Pprod
  • StopExecutionException (异常跳过)
  • enabled
kotlin 复制代码
tasks.register("oldTask") {
    // 禁用这个Task
    enabled = false
    
    doLast {
        println("这个Task已被禁用")
    }
}
  • timeout (超时跳过)
kotlin 复制代码
tasks.register("longRunningTask") {
    timeout.set(Duration.ofSeconds(10))
    
    doLast {
        println("开始执行长时间任务")
        Thread.sleep(15000)
        println("任务完成")
    }
}

执行结果:

arduino 复制代码
Execution failed for task ':longRunningTask'.
> Timeout has been exceeded

Task执行状态

运行项目 有些Task后边会带一些大写后缀,但是有些Task可能后边没有,一般是指运行说明

ruby 复制代码
> Task :LiveBundle:LiveAudience:mergeReleaseNativeLibs NO-SOURCE
> Task :LiveBundle:LiveAudience:stripReleaseDebugSymbols NO-SOURCE
> Task :LiveBundle:LiveAudience:copyReleaseJniLibsProjectOnly UP-TO-DATE
> Task :TingMainHost:Application:mergeAnd-f5-ocpaReleaseAssets FROM-CACHE
> Task :LiveBundle:Anchor:compileReleaseKotlin UP-TO-DATE
> Task :LiveBundle:LiveHome:compileReleaseKotlin UP-TO-DATE
标签 说明 示例场景
(无标签) Task正常执行 编译、打包等
UP-TO-DATE 输入输出未改变,跳过执行 增量构建
FROM-CACHE 从构建缓存中复用结果 开启Build Cache
SKIPPED Task被跳过 条件不满足、被排除
NO-SOURCE 没有源文件需要处理 空的源码目录

Task增量构建

增量构建 是当Task的输入和输出没有变化时,跳过action的执行,当Task输入或输出发生变化时,在action中只对发生变化的输入或输出进行处理,这样就可以避免一个没有变化的Task被反复构建,当Task发生变化时也只处理变化部分,这样可以提高Gradle的构建效率,缩短构建时间。

Task增量构建判断流程:

  1. 检查输入是否改变(文件内容、属性值等)
  2. 检查输出是否存在
  3. 如果输入未变且输出存在 → UP-TO-DATE
  4. 否则 → 重新执行

模拟示例:

kotlin 复制代码
abstract class IncrementalTask : DefaultTask() {
    
    @InputFile
    abstract val inputFile: RegularFileProperty
    
    @OutputFile
    abstract val outputFile: RegularFileProperty
    
    @TaskAction
    fun execute() {
        val input = inputFile.get().asFile
        val output = outputFile.get().asFile
        
        println("处理文件:${input.name}")
        
        // 读取输入,处理后写入输出
        val content = input.readText()
        output.writeText(content.uppercase())
    }
}

tasks.register<IncrementalTask>("processFile") {
    inputFile.set(file("input.txt"))
    outputFile.set(file("build/output.txt"))
}

首次执行:

bash 复制代码
$ ./gradlew processFile

> Task :processFile
处理文件:input.txt

再次执行(input.txt未改变):

bash 复制代码
$ ./gradlew processFile
// 发现文件没有改变 跳过执行
> Task :processFile UP-TO-DATE   

修改input.txt后再次执行:

bash 复制代码
$ ./gradlew processFile
// 发现文件产生了改变  重新执行 
> Task :processFile
处理文件:input.txt

关于输入和输出,上边的自定义Task注解代表的含义简单列举下常用的: 常用的注解

注解 作用 示例
@Input 标记输入属性 字符串、数字等
@InputFile 标记输入文件 单个文件
@InputFiles 标记输入文件集合 多个文件
@InputDirectory 标记输入目录 目录
@OutputFile 标记输出文件 单个文件
@OutputFiles 标记输出文件集合 多个文件
@OutputDirectory 标记输出目录 目录
@Internal 标记内部属性(不参与增量构建) 临时变量

禁用增量构建

kotlin 复制代码
tasks.register("alwaysRun") {
    // 禁用UP-TO-DATE检查
    outputs.upToDateWhen { false }
    
    doLast {
        println("每次都执行")
    }
}

最后

主要学习了 Task 基础知识吧,都是为了最后的自定义插件工作的,基础但是是必要的。

相关推荐
枣把儿42 分钟前
「zotepad」用Gemini3pro写出一个高效写作和发文的记事本应用
android·前端·nuxt.js
前端开发爱好者44 分钟前
VSCode 推出 绿色版!更强!更智能!
前端·javascript·visual studio code
小熊哥^--^1 小时前
WebSocket客户端封装类
前端·websocket
技术摆渡人1 小时前
Android 系统技术探索(5)指尖的舞蹈(Input 系统与 ANR)
android
来碗疙瘩汤1 小时前
uniapp动态读取版本号
android
四眼肥鱼1 小时前
全网最全的 qiankun 基于 react18+(主应用)、vue3.4+(微应用)实现页签缓存,页面缓存
前端·javascript
dorisrv1 小时前
优雅地处理前端错误边界
前端
狗哥哥1 小时前
Pinia Store 平滑迁移:用代理模式实现零风险重构
前端·架构
老前端的功夫2 小时前
前端水印技术深度解析:从基础实现到防破解方案
开发语言·前端·javascript·前端框架