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 基础知识吧,都是为了最后的自定义插件工作的,基础但是是必要的。

相关推荐
万少1 天前
HarmonyOS官方模板集成创新活动-流蓝卡片
前端·harmonyos
-To be number.wan1 天前
C++ 赋值运算符重载:深拷贝 vs 浅拷贝的生死线!
前端·c++
噢,我明白了1 天前
JavaScript 中处理时间格式的核心方式
前端·javascript
纸上的彩虹1 天前
半年一百个页面,重构系统也重构了我对前端工作的理解
前端·程序员·架构
李艺为1 天前
根据apk包名动态修改Android品牌与型号
android·开发语言
be or not to be1 天前
深入理解 CSS 浮动布局(float)
前端·css
LYFlied1 天前
【每日算法】LeetCode 1143. 最长公共子序列
前端·算法·leetcode·职场和发展·动态规划
老华带你飞1 天前
农产品销售管理|基于java + vue农产品销售管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端
小徐_23331 天前
2025 前端开源三年,npm 发包卡我半天
前端·npm·github
Tom4i1 天前
【网络优化】Android 如何监听系统网络连接成功
android·网络