Gradle基础 构建生命周期和Hook技术

26 篇文章 1 订阅
2 篇文章 0 订阅

https://juejin.im/post/5afec54951882542715001f2 

对于初学者来说,面对各种各样的Gradle构建脚本,想要梳理它的构建流程,往往不知道从何入手。Gradle的构建过程有着固定的生命周期,理解Gradle的生命周期和Hook点,有助于帮你梳理、扩展项目的构建流程。

构建的生命周期

任何Gradle的构建过程都分为三部分:初始化阶段、配置阶段和执行阶段。

初始化阶段

初始化阶段的任务是创建项目的层次结构,并且为每一个项目创建一个Project实例。 与初始化阶段相关的脚本文件是settings.gradle(包括<USER_HOME>/.gradle/init.d目录下的所有.gradle脚本文件,这些文件作用于本机的所有构建过程)。一个settings.gradle脚本对应一个Settings对象,我们最常用来声明项目的层次结构的include就是Settings类下的一个方法,在Gradle初始化的时候会构造一个Settings实例对象,它包含了下图中的方法,这些方法都可以直接在settings.gradle中直接访问。

Settings.java

 

比如可以通过如下代码向Gradle的构建过程添加监听:

 

gradle.addBuildListener(new BuildListener() {
  void buildStarted(Gradle var1) {
    println '开始构建'
  }
  void settingsEvaluated(Settings var1) {
    println 'settings评估完成(settins.gradle中代码执行完毕)'
    // var1.gradle.rootProject 这里访问Project对象时会报错,还未完成Project的初始化
  }
  void projectsLoaded(Gradle var1) {
    println '项目结构加载完成(初始化阶段结束)'
    println '初始化结束,可访问根项目:' + var1.gradle.rootProject
  }
  void projectsEvaluated(Gradle var1) {
    println '所有项目评估完成(配置阶段结束)'
  }
  void buildFinished(BuildResult var1) {
    println '构建结束 '
  }
})
复制代码

执行gradle build,打印结果如下:

settings评估完成(settins.gradle中代码执行完毕)
项目结构加载完成(初始化阶段结束)
初始化结束,可访问根项目:root project 'GradleTest'
所有项目评估完成(配置阶段结束)
:buildEnvironment

------------------------------------------------------------
Root project
------------------------------------------------------------

classpath
No dependencies

BUILD SUCCESSFUL

Total time: 0.959 secs
构建结束 
复制代码

配置阶段

配置阶段的任务是执行各项目下的build.gradle脚本,完成Project的配置,并且构造Task任务依赖关系图以便在执行阶段按照依赖关系执行Task。 该阶段也是我们最常接触到的构建阶段,比如应用外部构建插件apply plugin: 'com.android.application',配置插件的属性android{ compileSdkVersion 25 ...}等。每个build.gralde脚本文件对应一个Project对象,在初始化阶段创建,Project接口文档。 配置阶段执行的代码包括build.gralde中的各种语句、闭包以及Task中的配置段语句,在根目录的build.gradle中添加如下代码:

println 'build.gradle的配置阶段'

// 调用Project的dependencies(Closure c)声明项目依赖
dependencies {
    // 闭包中执行的代码
    println 'dependencies中执行的代码'
}

// 创建一个Task
task test() {
  println 'Task中的配置代码'
  // 定义一个闭包
  def a = {
    println 'Task中的配置代码2'
  }
  // 执行闭包
  a()
  doFirst {
    println '这段代码配置阶段不执行'
  }
}

println '我是顺序执行的'
复制代码

调用gradle build,得到如下结果:

build.gradle的配置阶段
dependencies中执行的代码
Task中的配置代码
Task中的配置代码2
我是顺序执行的
:buildEnvironment

------------------------------------------------------------
Root project
------------------------------------------------------------

classpath
No dependencies

BUILD SUCCESSFUL

Total time: 1.144 secs
复制代码

**一定要注意,配置阶段不仅执行build.gradle中的语句,还包括了Task中的配置语句。**从上面执行结果中可以看到,在执行了dependencies的闭包后,直接执行的是任务test中的配置段代码(Task中除了Action外的代码段都在配置阶段执行)。 另外一点,无论执行Gradle的任何命令,初始化阶段和配置阶段的代码都会被执行。 同样是上面那段Gradle脚本,我们执行帮助任务gradle help,任然会打印出上面的执行结果。我们在排查构建速度问题的时候可以留意,是否部分代码可以写成任务Task,从而减少配置阶段消耗的时间。

执行阶段

在配置阶段结束后,Gradle会根据任务Task的依赖关系创建一个有向无环图,可以通过Gradle对象的getTaskGraph方法访问,对应的类为TaskExecutionGraph,然后通过调用gradle <任务名>执行对应任务。

下面我们展示如何调用子项目中的任务。

  1. 在根目录下创建目录subproject,并添加文件build.gradle
  2. 在settings.gradle中添加include ':subproject'
  3. 在subproject的build.gradle中添加如下代码
task grandpa {
  doFirst {
    println 'task grandpa:doFirst 先于 doLast 执行'
  }
  doLast {
    println 'task grandpa:doLast'
  }
}

task father(dependsOn: grandpa) {
  doLast {
    println 'task father:doLast'
  }
}

task mother << {
  println 'task mother 先于 task father 执行'
}

task child(dependsOn: [father, mother]){
  doLast {
    println 'task child 最后执行'
  }
}

task nobody {
  doLast {
    println '我不执行'
  }
}
// 指定任务father必须在任务mother之后执行
father.mustRunAfter mother
复制代码

它们的依赖关系如下:

:subproject:child
+--- :subproject:father
|    \--- :subproject:grandpa
\--- :subproject:mother
复制代码

执行gradle :subproject:child,得到如下打印结果:

:subproject:mother
task mother 先于 task father 执行
:subproject:grandpa
task grandpa:doFirst 先于 doLast 执行
task grandpa:doLast
:subproject:father
task father:doLast
:subproject:child
task child 最后执行

BUILD SUCCESSFUL

Total time: 1.005 secs
复制代码

因为在配置阶段,我们声明了任务mother的优先级高于任务father,所以mother先于father执行,而任务father依赖于任务grandpa,所以grandpa先于father执行。任务nobody不存在于child的依赖关系中,所以不执行。

Hook点

任何Gradle的构建过程都分为三部分:初始化阶段、配置阶段和执行阶段。

**一定要注意,配置阶段不仅执行build.gradle中的语句,还包括了Task中的配置语句。**从上面执行结果中可以看到,在执行了dependencies的闭包后,直接执行的是任务test中的配置段代码(Task中除了Action外的代码段都在配置阶段执行)。 另外一点,无论执行Gradle的任何命令,初始化阶段和配置阶段的代码都会被执行

å¾ç

 

 

dependOn DAG

https://blog.csdn.net/sergeycao/article/details/54341813 使用dependOn属性将任务插入到有向无环图中。 讨论 在初始化阶段,Gradle根据它们的依赖性将任务组合成一个序列。结果是DAG。例如,Gradle文档形成了Java插件的DAG,如图4-1所示。

自定义Task并使得原有Task依赖它--afterEvaluate

app的buile.gradle

apply from: rootProject.file('tasks/tasks1.gradle')
project.afterEvaluate {
    println 'app的build文件************************* '
    assembleDebug.dependsOn beforeAssembleDebug
}

task1.gradle

task afterAssembleDebug() {
    doLast {
        println '111doLast afterAssembleDebug'
    }
}

自定义Task并依赖原有Task--whenTaskAdded

https://blog.csdn.net/zhaoyanjun6/article/details/78523958?locationNum=3&fps=1 在 app的 build.gradle 添加 apply from:"../util.gradle" 这样在 app的build.gradle 就可以用 copyFile task 了

//在app的build.gradle中添加如下 apply from:"../util.gradle" //在task被添加的时候定义依赖关系

tasks.whenTaskAdded {
    task ->
        if (task.name.contains("assembleDebug")) {
            task.getDependsOn().add({
                println 'app getDependsOn doLast afterAssembleDebug'
                afterAssembleDebug//可以直接执行这个task或者说是依赖
            })
        }
}

util.gradle如图 图片

 

Gradle的执行顺序和生命周期函数

gradle的解析顺序如下

rootproject 的setting.gradle,然后是rootproject的build.gradle,然后是各个subproject。所以project下的build.gradle会先于app下的build.gradle。 在build.gradle中,我们可以通过apply plugin:*** 引入插件,也可以通过 apply from ***.gradle引入其他gradle脚本中的函数定义或task等。

gradle里的钩子

一般hook我们指的是gradle的生命周期: 在解析setting.gradle之后,开始解析build.gradle之前,这里如果要干些事情(更改build.gradle校本内容),可以写在beforeEvaluate

举个例子,我们将我们的一个subproject中的apply plugin改掉,原来是一个library工程,我们希望它被当作application处理:

    project.beforeEvaluate {
                // Change android plugin from `lib' to `application' dynamically
                // FIXME: Any better way without edit file?
    
                if (mBakBuildFile.exists()) {
                    // With `tidyUp', should not reach here
                    throw new Exception("Conflict buildFile, please delete file $mBakBuildFile or " +
                            "${project.buildFile}")
                }
    
                def text = project.buildFile.text.replaceAll(
                        'com\\.android\\.library', 'com.android.application')
                project.buildFile.renameTo(mBakBuildFile)
                project.buildFile.write(text)
            }

在所有build.gradle解析完成后,开始执行task之前,此时所有的脚本已经解析完成,task,plugins等所有信息可以获取,task的依赖关系也已经生成,如果此时需要做一些事情,可以写在afterEvaluate

project.afterEvaluate {
            // Set application id
            def manifest = new XmlParser().parse(project.android.sourceSets.main.manifestFile)
            project.android.defaultConfig.applicationId = manifest.@package
        }

每个task都可以定义doFirst,doLast,用于定义在此task执行之前或之后执行的代码

project.assemble.doLast {
                    println "assemble finish"
                }
project.assemble.doFirst {
                    println "assemble start"
                }

全面理解Gradle - 执行时序

https://blog.csdn.net/singwhatiwanna/article/details/78797506 Gradle脚本的执行分为三个过程:

初始化 

分析有哪些module将要被构建,为每个module创建对应的 project实例。这个时候settings.gradle文件会被解析。

配置

处理所有的模块的 build 脚本,处理依赖,属性等。这个时候每个模块的build.gradle文件会被解析并配置,这个时候会构建整个task的链表(这里的链表仅仅指存在依赖关系的task的集合,不是数据结构的链表)。

执行

根据task链表来执行某一个特定的task,这个task所依赖的其他task都将会被提前执行。

Gradle插件

https://benweizhu.gitbooks.io/gradle-best-practice/content/plugin.html

脚本插件,其实就是存在另一个脚本文件(other.gradle)的一段脚本代码,通常情况下存放在同一个构建项目下,主要作用是抽取逻辑,让关注点分离(separate of concern)。 二进制插件,则是编译后的class文件,它们通过实现Gradle API中的Plugin接口,通过编程的方式操作构建过程。二进制插件可以在直接在构建脚本(build.gradle)中写,也可以是一个单独的project中(独立的项目模块),又或者来自于一个Jar文件(最常用的做法)。 插件的使用

apply from: 'other.gradle' // 使用脚本插件
apply plugin: 'java' // 使用二进制插件

使用插件的方式是调用Project对象的Project.apply(java.util.Map)方法。在二进制插件的使用中,传入的参数(比如‘java’),叫做plugin id。这个id必须是唯一的,一些核心的插件,Gradle给他们提供了简短的名字,比如:java。而社区的插件,名字则会采用完整名字,比如:me.zeph.database。

Android Gradle复制打包的apk到固定目录

https://blog.csdn.net/guijiaoba/article/details/42655437

    apply plugin: 'com.android.application'  

    android {
    // *** 
    }  

    dependencies {
    // ***** 
    }

    // 下面的代码才是最主要的*** 
    build {
         
          doLast {
            def fileName = "app-release.apk"        
            def fromFile = "./build/outputs/apk/" + fileName        
            def intoFile = "./outapks/"            
            def applicationId = android.defaultConfig.applicationId        
            def versionName = android.defaultConfig.versionName        
            def time = new java.text.SimpleDateFormat("yyyyMMddHHmmss").format(new Date())    
            def buildType = "realse"        
            def channel = "site"            
            def appName = "${applicationId}v${versionName}${time}${buildType}${channel}.apk"        
                // copy --> rename        
            copy {
                           
                from fromFile            
                into intoFile                
                rename {                                
                    appName            
                }        
            }            
            println("=====================build.doLast success.=========================")    
        }
    }

基本概念(Project 和 Task)

http://www.blogjava.net/wldandan/archive/2012/06/27/381605.html doLast意思是定义一个行为(映射Gradle中的Action类),放在当前task的最后,类似的,还有doFirst, 表示将定义的行为放在当前task最前面,例如

task hello(){

    doLast {
          println   "Hello world"        
    }          

    doFirst {
        println   "I am xxx"      
    }      
} 

执行gradle hello, 将输出 "I am xxx" "Hello world"

另外,你也可以使用如下更简洁的方式来定义task:

task hello <<  {      println "hello world" }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值