Gradle & AGP 详解

45 阅读7分钟

一、Gradle 生命周期与生命周期的hook

1、生命周期的三个阶段

初始化阶段(Initialization):决定哪些项目参与编译。 Gradle会决定构建中包含哪些项目,并会为每个项目创建Project实例。为了决定构建中会包含哪些项目,Gradle首先会寻找settings.gradle来决定此次为单项目构建还是多项目构建。

配置阶段(Configuration):解析配置,会生成Task注册表。 在 Configuration (配置) 阶段,Gradle会评估构建项目中包含的所有构建脚本,随后应用插件、使用DSL配置构建,并在最后注册Task。简单的说,配置阶段就是创建Projec对象,执行我们的build.gradle文件,根据代码创建对应的Task依赖关系图。

Gradle执行阶段(Execution):依次执行Task。 Gradle会执行构建所需的Task集合

总结: 简单来说,就是Gradle通过settings.gradle在初始化阶段决定哪些项目参与编译,确定后每个project在配置阶段解析配置,会生成Task注册表。最后,在执行阶段依次执行Task

2、Hook生命周期

Gradle在生命周期各个阶段都提供了用于回调的钩子函数,如下图:

image.png

image.png

初始化阶段(Initialization):

在settings.gradle执行完后,会回调Gradle对象的settingsEvaluated方法;
在构建所有工程build.gradle对应的Project对象后,即初始化阶段完毕,会回调Gradle对象的projectsLoaded方法。

配置阶段(Configuration):

Gradle会循环执行每个工程的build.gradle脚本文件;
在执行当前工程build.gradle前,会回调Gradle对象的beforeProject方法和当前Project对象的beforeEvaluate方法。虽然beforeEvalute属于project的生命周期,但是此时buildscript尚未被加载,所以beforeEvaluate的设置依然要在init script或setting script中进行,不要在 build script中使用project.beforeEvaluate方法;
在执行当前工程build.gradle后,会回调Gradle对象的afterProject方法和当前Project对象的afterEvaluate方法;
在所有工程的build.gradle执行完毕后,会回调Gradle对象的projectsEvaluated方法;
在构建Task依赖有向无环图后,也就是配置阶段完毕,会回调TaskExecutionGraph对象的whenReady方法。

Gradle执行阶段(Execution):

Gradle会循环执行Task及其依赖的Task;
在当前Task执行之前,会回调TaskExecutionGraph对象的beforeTask方法;
在当前Task执行之后,会回调TaskExecutionGraph对象的afterTask方法;
当所有的Task执行完毕后,会回调Gradle对象的buildFinish方法。

提示:Gradle执行脚本文件的时候会生成对应的实例,主要有如下几种对象:

Gradle对象:在项目初始化时构建,全局单例存在,只有这一个对象;
Project对象:每一个build.gradle文件都会被转换成一个Project对象,类似于maven中的pom.xml文件;
Settings对象:settings.gradle会转变成一个settings对象,和整个项目是一对一的关系,一般只用到include方法;
Task对象: 从前面的有向无环图中,我们也可以看出,gradle最终是基于Task的,一个项目可以有一个或者多个Task。

3、案例

setting.gradle

println("---Gradle:初始化阶段")
gradle.settingsEvaluated {
    println("---Gradle:settingsEvaluated Settings对象评估完毕")
}
gradle.projectsLoaded {
    println("---Gradle:projectsLoaded 准备加载Project对象了")
    println("---Gradle:初始化阶段结束")
    println("---Gradle:配置阶段开始")
}
 
 
gradle.projectsEvaluated {
    println("---Gradle:projectsEvaluated 所有Project对象评估完毕")
    println("---Gradle:配置阶段结束")
}
 
gradle.allprojects{
 
    beforeEvaluate {
        println("---Gradle:Projec beforeEvaluate Project开始评估,对象是 = "+project.name)
    }
    afterEvaluate {
        println("---Gradle:Projec afterEvaluate Project评估完毕,对象是 = " + project.name)
    }
}
 
gradle.taskGraph.whenReady {
    println "---Gradle:taskGraph whenReady打印task ...start"
    gradle.taskGraph.getAllTasks().each {task ->
         println("${task.path}")
    }
    println "---Gradle:taskGraph whenReady打印task ...end"
    println("---Gradle:执行阶段开始,开始执行task")
}
 
gradle.taskGraph.beforeTask {
    task -> println "---Gradle:task: ${task.name} of the project ${task.getProject().name} beforeTask.."
}
 
gradle.taskGraph.afterTask {
    task -> println "---Gradle:task: ${task.name} of the project ${task.getProject().name} afterTask.."
}
 
gradle.buildFinished {
    println("---Gradle:buildFinished 构建结束了")
    println("---Gradle:执行阶段结束")
}

参考文件 blog.csdn.net/qq_37475168…

二、Gradle Task 

1、是什么

Task是一个任务,是Gradle中最小的构建单元。Task管理了一个Action的List,你可以在List前面插入一个Action(doFirst),也可以从list后面插入(doLast),Action是最小的执行单元

Task是Gradle构建的核心对象,Task可以接收输入、执行某些操作,并根据执行的操作产生输出。

2、思考

Gradle构建的核心就是在配置阶段构建了由Task组成的有向无环图

因此,我们在学习、使用Task也是基于这个有向无环图,插入TasK,或者在现有Task中插入Action

3、学习方向

Task 创建

Task插入当前有向无环图中

在现有Task的基础上插入Action

4、案例

在现有Task中插入Action:统计build耗时

//统计build 阶段耗时
project.afterEvaluate {
    println("---配置阶段打印 " + project.name)
    def startTime
    def endTime
    def perBuildTask = project.tasks.getByName("preBuild")
    def buildTask = project.tasks.getByName("assembleOverseasDebug")
    //在构建开始,结束时插入Action,计算构建时间
    //Action1 在项目构建开始见记录开始时间
    perBuildTask.doFirst {
        startTime = System.currentTimeMillis()
        println("---perBuildTask任务开始前" + startTime)
    }
    //Action2 在项目构建结束后记录结束时间
    buildTask.doLast {
        endTime = System.currentTimeMillis()
        println("---buildTask结束后" + endTime)
    }
}

Task插入到当前的有向无环图中

task taskA {
    doLast {
        println "taskA 执行"
    }
}
 
task taskB {
 
    doLast {
        println "taskB 执行"
    }
}
 
//在preBuild后面插入一个任务
project.afterEvaluate {
//    dependsOn: ATask.dependsOn(B)   A的执行依赖B B要在A前先执行
//    finalizedBy: ATask.finalizedBy(B)   A的执行后再执行B B要在A后执行
//    mustRunAfter:  ATask.mustRunAfter(B)  A的执行一定要再B后面 B要在A前先执行
 
    // 执行顺序 firstTask->taskA->secondTask
    def firstTask = project.tasks.findByName("packageOverseasDebug")
    def secondTask = project.tasks.findByName("assembleOverseasDebug")
 
    if(firstTask!= null) {
        taskA.mustRunAfter(firstTask)
    }
    if(secondTask!=null){
        secondTask.dependsOn(secondTask)
    }
 
    // 执行顺序 cTask->taskB
    def cTask = project.tasks.findByName("assembleOverseasDebug")
    if(cTask!= null){
        cTask.finalizedBy(taskB)
    }
}
 https://juejin.cn/post/6982379643311489032

自己编写插件

理解了Gradle的运行原理,有了问题处理的思路,明白了自己要在哪个阶段去处理,问题,剩下的操作,就是去写代码了 ,这里推荐参考Gradle 官方案例(github.com/android/gra…) 根据自己的需求,去写插件

上面的内容讲述的都是Gradle插件 ,下面要讲的是APG,Gradle PluginGradle构建过程中使用的插件的总称,而Android Gradle Plugin是这个总称里面的一个插件元素.AGP配合Gradle构建我们的应用apk

三、AGP(Android Gradle Plugin)

1、是什么

为什么要有AGP,因为AGP是官方编写的,专门针对Android应用构建流程的一个插件,这个插件的内部task是不对外暴露的,因此我们想修改一个应用的构建流程,无法通过Gradle Plugin 插件来影响其内部的task,因此需要学习APG ,使用其提供的API,来完成影响构建过程的目的

他摒弃了原有Gradle 生命周期流程(通过在 现有task的基础上,插入自定义task的操作方式影响构建结果),转而使用了自定义的生命周期,以及从工件的角度,将task的细节隐藏,开发者只要专注于构建产物,而无需关注task之间的依赖关系,在一定程度上降低了开发难度

官方指导文档:developer.android.com/studio/buil…

2、AGP生命周期

image.png

image.png

3、核心概念

重点学习对象:Variant API、工件(Artifact)和任务

工件(Artifact):Artifact 翻译过来意思是手工艺品、工件、手工制品,它就是代表着Android构建过程中的一些中间产物,如 manifest、aar、class、apk等等。

Variant API:对工件进行加工的API,其专心于产品而不是Task,通过API访问变体对象和一些中间件,以方便用户在不接触任务的情况下影响build的行为,开发者不需求知道要修正的产品依靠于哪个Task,也不需求知道Task的详细完结,能够有效降低咱们开发与晋级Gradle插件的成本。

简单理解:工件(Artifact)是核心,Variant API提供了一系列的操作方法,去加工工件,最终将工件加工成我们想要的样子

官网文档:developer.android.com/studio/buil…

4、思考

AGP的核心是工件(Artifact)

--我们可以对一个工件做哪些操作

--如何将自己的流程,插入到工件当中

5、案例

so匹配检测

/***
    * 通过AGP方案实现
    * Created by 11070535 at 2023/10/18
    */
   private fun startPlugin(project: Project) {
       if (project.plugins.findPlugin("com.android.application") == null) {
           return
       }
       printlnWapper("SoCheckPlugin")
       extension = project.extensions.create(EXTENSION_NAME, SoCheckExtension::class.java)
 
       val androidExtension = project.extensions.getByType(AndroidComponentsExtension::class.java)
       //1. 获取或创建 SoCheckExtension 对象
       androidExtension.finalizeDsl {
           if (extension.analysisSo) {
               printlnWapper("已开启so打印")
           }
       }
 
       androidExtension.onVariants { variant ->
           if (!extension.analysisSo) {
               return@onVariants
           }
           //延迟创建soTask
           val taskProvider =
               project.tasks.register(variant.name + "SoCheck", SoCheckTask::class.java) { task ->
                   task.doFirst {
                       printlnWapper("开始分析APK so")
                   }
                   task.builtArtifactsLoader.set(variant.artifacts.getBuiltArtifactsLoader())
                   task.doLast {
                       printlnWapper("结束分析APK so")
                   }
               }
 
           //将Task 结合到构建流程当中
           variant.artifacts.use(taskProvider)
               .wiredWithDirectories(
                   SoCheckTask::apkPath,
                   SoCheckTask::apkFolder
               )
               .toTransform(SingleArtifact.APK)
       }
   }

自定义了一个 variant.name + "SoCheck" TASK ,并将task 插入到了构建过程当中

进阶优化

延迟初始化

一个好的插件,不是一股脑把所有问题都在配置阶段给做了,对于一些过于耗时的部分,需要放到后面执行阶段去实现是最好的,

Provider<T> 延迟初始化只读变量

Property<T> 延迟初始化读写变量

TaskProvider 延迟初始化Task

这里简单理解为用到的时候在使用,,生成的Task不会立即执行,只有他被使用时,才会执行相关逻辑 具体使用可以看案例,不做过多阐述