gradle深入研究

Gradle

1.基本元素

Project

每个项目的编译至少有一个 Project,一个 build.gradle就代表一个project,每个project里面包含了多个task,task里面又包含很多action,action是一个代码块,里面包含了需要被执行的代码。

Script

gradle的脚本文件,通过脚本,我们可以定义一个Project

Task

Project中的具体执行的原子性工作,以构建一个工程为例,它可以是 编译,执行单元测试,发布 等。

2.Script元素

Init Script

似乎从来没有使用过,但是在每一次构建开始之前,都会执行init script,用来设置一些全局变量,有多个位置可以存放init script如下:

USER_HOME/.gradle/
USER_HOME/.gradle/init.d/
GRADLE_HOME/init.d/
Settings Script

用来在组织多工程的构建,存在于root工程下,settings.gradle,用于生命该工程都包含哪些project

上述script在运行时都会被编译成一个实现了Script接口的class,同时每一个script都有一个委托对象

Build Script -> Project

Init Script  -> Gradle

Settings Script -> Settings
Build Script

每一个build.gradle都是一个Build Scrpit,它由两种元素组成。

statement

可以包含方法调用,属性赋值,局部变量定义等.

script blocks

block的概念稍微复杂一点,首先我们先要理解一个groovy的元素,闭包.

有了闭包的概念,那么理解script block就没有障碍了,直接看文档中的定义:

A script block is a method call which takes a closure as a parameter. The closure is treated as a configuration closure which configures some delegate object as it executes.

翻译一下就是

一个脚本块是一个接受一个闭包作为参数的方法,这个闭包在执行的时候配置它的委托对象。

举个例子🌰

def buildVersion = '1.2.0'
def author = 'liuboyu'
allprojects {
    repositories {
        jcenter()
    }
    setVersion(buildVersion)
    println "this project_${name}_${getVersion()} is created by ${author}"
}

首先我们定义了两个变量分别是buildVersion和author,在执行时这个两个变量会成为Script Class的属性。然后,我们使用了一个script block,根据定义,这个block对应着一个同名方法allprojects,可是我们并没有在脚本中定义这样一个方法,那它如何执行呢?回想一下我们刚刚看到的build script的委托对象,没错,这个方法被委托给了Project对象执行,查看文档,我们确实在Project中找到了这个同名方法.

接下来,我们在块中写了两行代码,这就是这个闭包需要执行的代码,首先打印一行文字,其次setVersion()。同样的,我们没有定义setVersion这个方法,这就涉及到闭包的一些概念,我们换一种写法

def buildVersion = '1.2.0'
def author = 'liuboyu'
allprojects {
    repositories {
        jcenter()
    }
    delegate.setVersion(buildVersion)
    println "this project_${delegate.name}_${delegate.getVersion()} is created by ${author}"
}

setVersion 这个方法实际上是由闭包的委托对象执行的,那委托对象是什么呢?我们查阅一下allprojects这个方法的Api,如[api文档](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:allprojects(groovy.lang.Closure)
[图片上传失败...(image-76ac25-1537328769306)]
这个闭包的委托对象是当前的project和它的子project,也就是对于一个包含子工程的工程,这个闭包会执行多次,我们实验一下

this project_GradleDeepTest_1.2.0 is created by liuboyu
this project_app_1.2.0 is created by liuboyu
this project_testlibrary_1.2.0 is created by liuboyu

闭包中的 Owner,delegate,this

闭包内部通常会定义一下3种类型:

  • this corresponds to the enclosing class where the closure is defined
  • this 对应于闭包定义处的封闭类
  • owner corresponds to the enclosing object where the closure is defined, which may be either a class or a closure
  • owner 对应于闭包定义处的封闭对象(可能是一个类或者闭包)
  • delegate corresponds to a third party object where methods calls or properties are resolved whenever the receiver of the message is not defined
  • delegate 对应于方法调用或属性处的第三方对象,无论消息接收者是否定义。
this

在闭包中,调用getThisObject将会返回闭包定义处所处的类。等价于使用显示的this:

class Enclosing {
    void run() {
        // 定义在Enclosing类中的闭包,并且返回getThisObject
        def whatIsThisObject = { getThisObject() }

        // 调用闭包将会返回一个 闭包定义处的类的Enclosing的实例
        assert whatIsThisObject() == this

        // 可以使用简洁的this符号
        def whatIsThis = { this }

        // 返回同一个对象
        assert whatIsThis() == this
        println("Enclosing success " + this)
    }
}
class EnclosedInInnerClass {
    class Inner {
        // 内部类中的闭包
        Closure cl = { this }
    }
    void run() {
        def inner = new Inner()
        // 在内部类中的this将会返回内部类,而不是顶层的那个类。
        assert inner.cl() == inner
        println("EnclosedInInnerClass success")
    }
}
class NestedClosures {
    void run() {
        def nestedClosures = {
            // 闭包内定义闭包
            def cl = { this }
            cl()
        }
        // this对应于最近的外部类,而不是封闭的闭包!
        assert nestedClosures() == this
    }
}
class Person {
    String name
    int age
    String toString() { "$name is $age years old" }

    String dump() {
        def cl = {
            String msg = this.toString()//在闭包中使用this调用toString方法,将会调用闭包所在封闭类对象的toString方法,也就是Person的实例
            println msg
        }
        cl()
    }
}
def p = new Person(name:'Janice', age:74)
assert p.dump() == 'Janice is 74 years old'
Owner

闭包中的owner和闭包中的this的定义非常的像,只不过有一点微妙的不同:它将返回它最直接的封闭的对象,可以是一个闭包也可以是一个类的:

class Enclosing2 {
    void run() {
        // 定义在Enclosing类中的闭包,getOwner
        def whatIsThisObject = { getOwner() }

        // 调用闭包将会返回一个 闭包定义处的类的Enclosing的实例
        assert whatIsThisObject() == this

        // 使用简洁的owner符号
        def whatIsThis = { owner }

        // 返回同一个对象
        assert whatIsThis() == this
        println("Enclosing2 success " + this)
    }
}
class EnclosedInInnerClass2 {
    class Inner {
        // 内部类中的闭包
        Closure cl = { owner }
    }
    void run() {
        def inner = new Inner()
        // 在内部类中的owner将会返回内部类,而不是顶层的那个类。
        assert inner.cl() == inner
        println("EnclosedInInnerClass success")
    }
}
class NestedClosures2 {
    void run() {
        def nestedClosures = {
            // 闭包内定义闭包
            def cl = { owner }
            cl()
        }
        // owner对应的是封闭的闭包,这是不同于this的地方
        assert nestedClosures() == nestedClosures
    }
}
Delegate

对于delegate来讲,它的含义大多数情况下是跟owner的含义一样,除非它被显示的修改(通过Closure.setDelegate()方法进行修改)。

class Enclosing3 {
    void run() {
        // 获得闭包的delegate可以通过调用getDelegate方法
        def cl = { getDelegate() }

        // 使用delegate属性
        def cl2 = { delegate }

        // 二者返回同样的对象
        assert cl() == cl2()

        // 是封闭的类或这闭包
        assert cl() == this

        // 特别是在闭包的内部的闭包
        def closure = {
            // 闭包内定义闭包
            def cl3 = { delegate }
            cl3()
        }

        // delegate对应于owner返回同样的对象或者闭包
        assert closure() == closure
    }
}
def scriptClosure={
            println "scriptClosure this:"+this
            println "scriptClosure owner:"+owner
            println "scriptClosure delegate:"+delegate
        }
        println "before setDelegate()"
        scriptClosure.call()
        scriptClosure.setDelegate ("abc")
        println "after setDelegate()"
        scriptClosure.call()

结果:

before setDelegate()
scriptClosure this:class Client2
scriptClosure owner:class Client2
scriptClosure delegate:class Client2
after setDelegate()
scriptClosure this:class Client2
scriptClosure owner:class Client2
scriptClosure delegate:abc

闭包的delegate可以被更改为任意的对象。先定义两个相互之间没有继承关系的类,二者都定义了一个名为name的属性:

class Person {
    String name
    def upperCasedName = { delegate.name.toUpperCase() }
}

def p1 = new Person(name:'Janice', age:74)
def p2 = new Person(name:'liuboYu', age:18)

然后,定义一个闭包通过delegate获取一下name属性:

p1.upperCasedName.delegate = p2
println(p1.upperCasedName())

然后,通过改变闭包的delegate,你可以看到目标对象发生了改变:

JANICE
LIUBOYU

委托机制

无论何时,在闭包中,访问一个属性,不需要指定接收对象,这时使用的是delegation strategy:

class Person {
    String name
}
def person = new Person(name:'Igor')
def cl = { name.toUpperCase() } //name不是闭包括号内的一个变量的索引
cl.delegate = person //改变闭包的delegate为Person的实例
assert cl() == 'IGOR'//调用成功

之所以可以这样调用的原因是name属性将会自然而然的被delegate的对象征用。这样很好的解决了闭包内部属性或者方法的调用。不需要显示的设置(delegate.)作为接收者:调用成功是因为默认的闭包的delegation strategy使然。闭包提供了多种策略方案你可以选择:

  • Closure.OWNER_FIRST 是默认的策略。如果一个方法存在于owner,然后他将会被owner调用。如果不是,然后delegate将会被使用
  • Closure.Delegate_FIRST 使用这样的逻辑:delegate首先使用,其次是owner
  • Closure.OWNER_ONLY 只会使用owner:delegate会被忽略
  • Closure.DELEGATE_ONLY 只用delegate:忽略owner
  • Closure.TO_SELF can be used by developers who need advanced meta-programming techniques and wish to implement a custom resolution strategy: the resolution will not be made on the owner or the delegate but only on the closure class itself. It makes only sense to use this if you implement your own subclass of Closure.

使用下面的代码来描绘一下”owner first”

class Person {
    String name
    def pretty = { "My name is $name" }  //定义一个执行name的闭包成员
    String toString() {
        pretty()
    }
}
class Thing {
    String name //类和Person和Thing都定义了一个name属性
}

def p = new Person(name: 'Sarah')
def t = new Thing(name: 'Teapot')

assert p.toString() == 'My name is Sarah'//使用默认的机制,name属性首先被owner调用
p.pretty.delegate = t  //设置delegate为Thing的实例对象t
assert p.toString() == 'My name is Sarah'//结果没有改变:name被闭包的owner调用

然而,改变closure的解决方案的策略改变结果是可以的:

p.pretty.resolveStrategy = Closure.DELEGATE_FIRST
assert p.toString() == 'My name is Teapot'

通过改变resolveStrategy,我们可以改变Groovy”显式this”的指向:在这种情况下,name将会首先在delegate中找到,如果没有发现则是在owner中寻找。name被定义在delegate中,Thing的实例将会被使用。

“delegate first”和”delegate only”或者”owner first”和”owner only”之间的区别可以被下面的这个其中一个delegate没有某个属性/方法的例子来描述:

class Person {
    String name
    int age
    def fetchAge = { age }
}
class Thing {
    String name
}
def p = new Person(name:'Jessica', age:42)
        def t = new Things(name:'Printer')
        def cl = p.fetchAge
        cl.delegate = p
        assert cl() == 42
        cl.delegate = t
        assert cl() == 42
        cl.resolveStrategy = Closure.DELEGATE_ONLY
        cl.delegate = p
        assert cl() == 42
        cl.delegate = t
        try {
            cl()
        } catch (MissingPropertyException ex) {
            println(" \"age\" is not defined on the delegate")
        }

在这个例子中,我们定义了两个都有name属性的类,但只有Person具有age属性。Person类同时声明了一个指向age的闭包。我们改变默认的方案策略,从”owner first”到”delegate only”。由于闭包的owner是Person类,如果delegate是Person的实例,将会成功调用这个闭包,但是如果我们调用它,且它的delegate是Thing的实例,将会调用失败,并抛出groovy.lang.MissingPropertyException。尽管这个闭包定义在Person类中,但owner没有被使用。

image

subprojects、dependencies、repositories 都是 script blocks,后面都需要跟一个花括号,通过查阅文档可以发现,其实就是个闭包。
我们通过源码可以查看

/**
   * <p>Configures the build script classpath for this project.
   *
   * <p>The given closure is executed against this project's {@link ScriptHandler}. The {@link ScriptHandler} is
   * passed to the closure as the closure's delegate.
   *
   * @param configureClosure the closure to use to configure the build script classpath.
   */
  void buildscript(@DelegatesTo(value = ScriptHandler.class, strategy = Closure.DELEGATE_FIRST) @ClosureParams(value = SimpleType.class, options = {"org.gradle.api.initialization.dsl.ScriptHandler"}) Closure configureClosure);

它的 closure 是在一个类型为 ScriptHandler 的对象上执行的。主意用来所依赖的 classpath 等信息。通过查看 ScriptHandler API 可知,在 buildscript SB 中,你可以调用 ScriptHandler 提供的 repositories(Closure )、dependencies(Closure)函数。这也是为什么 repositories 和 dependencies 两个 script blocks 为什么要放在 buildscript 的花括号中的原因。

repositories 表示代码仓库的下载来源,默认的来源是jcenter。

Gradle支持的代码仓库有几种类型:

  1. Maven中央仓库,不支持https访问,声明方法为mavenCentral()
  2. JCenter中央仓库,实际上也是用Maven搭建,通过CDN分发,并且支持https访问,也就是我们上面默认的声明方法:jcenter
  3. Maven本地仓库,可以通过本地配置文件配置,通过USER_HOME/.m2/下的settings.xml配置文件修改默认路径位置,声明方法为mavenLocal()
  4. 常规的第三方maven库,需要设置访问url,声明方法为maven,这个一般是有自己的maven私服
  5. Ivy仓库,可以是本地仓库,也可以是远程仓库
  6. 直接使用本地文件夹作为仓库

dependencies 表明项目依赖对应版本的Gradle构建工具,但更加具体的版本信息却是在gradle-wrapper.properties这个文件中,具体如:

#Tue Oct 31 15:31:02 CST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip

allprojects 指定所有参与构建的项目使用的仓库的来源。

这里我们有一个疑问:buildscript和allprojects都指定使用的仓库的来源,它们的真正区别在哪里呢?

  1. buildScript块的repositories主要是为了Gradle脚本自身的执行,获取脚本依赖插件。
  2. 根级别的repositories主要是为了当前项目提供所需依赖包,但在Android中,这个跟allprojects的repositories的作用是一样的。同样dependencies也可以是根级别的。
  3. allprojects块的repositories用于多项目构建,为所有项目提供共同所需依赖包。而子项目可以配置自己的repositories以获取自己独需的依赖包。

实际上,allprojects是用于多项目构建,在Android中,使用多项目构建,其实就是多Module构建。
我们看settings.gradle这个文件:

include ':app', ':testlibrary'

通过include将app这个module添加进来。从根目录开始,一直到include进来的所有module,都会执行allprojects的内容。
我们也可以通过subprojects来指定module和根目录不同的行为。
例如:

subprojects {
    println " ---${delegate.name}---subprojects------ "
}

上面的例子,只有子project会执行,root project并不会执行
或者我们也可以单独指定某个project自己的行为。
我们是否可以指定某个module执行它特有的行为呢?
例如:

project(':testlibrary'){
    println " ---testlibrary---subprojects------ "
}

project(':app'){
    println " ---app---subprojects------ "
}

Android自己也定义了好多ScriptBlock,请参考DSL参考文档

image

下图为 buildToolsVersion 和 compileSdkVersion 的说明:

image

接下来看一下

defaultConfig {
        applicationId "test.project.com.gradledeeptest"
        minSdkVersion 15
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
    }

[图片上传失败...(image-1b57f5-1537328769307)]

3.Gradle 组成

Gradle 主要有三种对象,这三种对象和三种不同的脚本文件对应,在 gradle 执行的时候,会将脚本转换成对应的对端:

  • Gradle 对象:当我们执行 gradle xxx 或者什么的时候,gradle 会从默认的配置脚本中构造出一个 Gradle 对象。在整个执行过程中,只有这么一个对象。Gradle 对象的数据类型就是 Gradle。我们一般很少去定制这个默认的配置脚本。
  • Project 对象:每一个 build.gradle 会转换成一个 Project 对象。
  • Settings 对象:显然,每一个 settings.gradle 都会转换成一个 Settings 对象。

4.Gradle 对象

[图片上传失败...(image-2f9871-1537328769307)]
我们写个例子,验证只有全局只有一个gradle,build.gradle 中和 settings.gradle 中分别加了如下输出:

println "setting In posdevice, gradle id is " + gradle.hashCode()
println "setting version: " + gradle.gradleVersion

得到结果如下所示:

setting In posdevice, gradle id is 1466991549
setting version: 4.1
library In posdevice, gradle id is 1466991549
library version: 4.1
app In posdevice, gradle id is 1466991549
app version: 4.1
  • 在 settings.gradle 和 posdevice build.gradle 中,我们得到的 gradle 实例对象的 hashCode 是一样的(都是 791279786)
  • gradleVersion输出当前 gradle 的版本

5.Project 对象

每一个 build.gradle 文件都会转换成一个 Project 对象。在 Gradle 术语中,Project 对象对应的是 Build Script。

Project 包含若干 Tasks。另外,由于 Project 对应具体的工程,所以需要为 Project 加载所需要的插件,比如为 Android 工程加载 android 插件。一个 Project 包含多少 Task 往往是插件决定的。

  • 创建一个Settings对象,
  • 根据settings.gradle文件配置它
  • 根据Settings对象中定义的工程的父子关系创建Project对象
  • 执行每一个工程的build.gradle文件配置上一步中创建的Project对
加载插件
image
apply plugin: 'com.android.library' <==如果是编译 Library,则加载此插件
apply plugin: 'com.android.application' <==如果是编译 Android APP,则加载此插件

除了加载二进制的插件(上面的插件其实都是下载了对应的 jar 包,这也是通常意义上我们所理解的插件),还可以加载一个 gradle 文件

apply from: "utils.gradle"

6.全局变量 ext

我们前面讲解了gradle的生命周期,在配置的过程中,整个项目会生成一个gradle 对象,每个build.gradle的文档都会生成一个project对象。这两个对象都有一个ext,这个ext的属性就类似于我们的钱包一样,独立属于gradle与project对象。我们可以往这个ext对象里面放置属性。

6.1 gradle 的ext对象

我们可以使用这样的方法存储一个变量,这个变量属于gradle,整个工程都能使用

    //第一次定义或者设置它的时候需要 ext 前缀
    gradle.ext.api = properties.getProperty('sdk.api')

    println gradle.api  //再次存取 api 的时候,就不需要 ext 前缀了

或者直接在 gradle.properties 文件中添加属性,作用域也是全局的
读取方式如下

task A{
    doLast{
         println(gradle.api)
    }
}
6.2 project 的ext对象

保存值

ext{
     myName ='sohu'
     age = 18
}

获取值,可以直接获取

println(myName)

上面这个代码 println(myName) 就等于println (project.ext.myName)

我们一般在ext内存储一些通用的变量,除此以外,我们也使用这个ext来做一些很酷的功能,比如说我们的gradle文件很大了,我们可以好像代码一下,进行抽取。
project 的ext对象 的作用域是当前的project

7.生命周期

1212336-5ab3ae4bc237b401.png
初始化阶段:

主要是解析 setting.gradle 文件,gradle支持单工程和多工程构建,在初始化的过程中,gradle决定了这次构建包含哪些工程,并且为每一个工程创建一个Project对象。并且,所有在Settings script中包含的工程的build script都会执行,因为gradle需要为每一个Project对象配置完整的信息。

读取配置阶段:

主要是解析所有的 projects 下的 build.gradle 文件,在配置的过程中,本次构建包含的所有工程的build script 都会执行一次,同时每个工程的Project对象都会被配置,运行时需要的信息在这个过程中被配置到Projec对象中。最重要的是,在build script中定义的task将在这个过程创建,并被初始化。需要注意的是,在一般情况下,只要在初始化阶段创建的Project对象都会被配置,即使这个工程没有参与本次构建。

执行阶段:

按照 2 中建立的有向无循环图来执行每一个 task ,整个编译过程中,这一步基本会占去 9 成以上的时间,尤其是对于 Android 项目来讲,将 java 转为 class.

8.项目应用

a. Gradle生命周期回调

gradle提供了对project状态配置监听的接口回调,以方便我们来配置一些Project的配置属性,监听主要分为两大类,一种是通过project进行回调,一种是通过gradle进行回调。

作用域:

  • project是只针对当前project实现进行的监听
  • gradle监听是针对于所有的project而言的

下面我们通过一个例子来看看gradle的构建生命周期究竟是怎么样的。

项目结构

root Project:GradleDeepTest
    -- app
        build.gradle
    -- testlibrary
        build.gradle
    build.gradle
    settings.gradle

root/settings.gradle

println "#### setting srcipt execute "
include ':app', ':testlibrary'

root/build.gradle

println "#### root build.gradle execute "

def buildVersion = '1.2.0'
def author = 'liuboyu'
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.0'
    }
}

allprojects {
    repositories {
        jcenter()
    }
//    it.setVersion(buildVersion)
//    println "this project_${delegate.name}_${delegate.getVersion()} is created by ${author}"
}


gradle.addProjectEvaluationListener(new ProjectEvaluationListener() {
    @Override
    void beforeEvaluate(Project project) {
        println "ROOT gradle_Project lifecycle : beforeEvaluate ${project.name} evaluate "
    }

    @Override
    void afterEvaluate(Project project, ProjectState state) {
        println "ROOT gradle_Project lifecycle : afterEvaluate ${project.name} evaluate "
    }
})

gradle.addBuildListener(new BuildListener() {
    @Override
    void buildStarted(Gradle gradle) {
        println "==ROOT== gradle Build lifecycle : buildStarted ${project.name} evaluate "
    }

    @Override
    void settingsEvaluated(Settings settings) {
        println "==ROOT== gradle Build lifecycle : settingsEvaluated ${project.name} evaluate "
    }

    @Override
    void projectsLoaded(Gradle gradle) {
        println "==ROOT== gradle Build lifecycle : projectsLoaded ${project.name} evaluate "
    }

    @Override
    void projectsEvaluated(Gradle gradle) {
        println "==ROOT== gradle Build lifecycle : projectsEvaluated ${project.name} evaluate "
    }

    @Override
    void buildFinished(BuildResult result) {
        println "==ROOT== gradle Build lifecycle : buildFinished ${project.name} evaluate "
    }
})

root/app/build.gradle

println "#### app build.gradle execute "
apply plugin: 'com.android.application'

android {
    compileSdkVersion 27
    buildToolsVersion "26.0.2"

    defaultConfig {
        applicationId "test.project.com.gradledeeptest"
        minSdkVersion 15
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

}

project.afterEvaluate {
    println "%%%% app lifecycle : afterEvaluate ${project.name} evaluate "
}

project.beforeEvaluate {
    println "%%%% app lifecycle : beforeEvaluate ${project.name} evaluate "
}


dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:22.2.0'
    compile 'com.android.support:design:22.2.0'
}

root/testlibrary/build.gradle

println "#### library build.gradle execute "
apply plugin: 'com.android.library'

android {
    compileSdkVersion 27
    buildToolsVersion "26.0.2"

    defaultConfig {
        minSdkVersion 15
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

project.afterEvaluate {
    println "@@@@ testlibrary lifecycle : afterEvaluate ${project.name} evaluate "
}

project.beforeEvaluate {
    println "@@@@ testlibrary lifecycle : beforeEvaluate ${project.name} evaluate "
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:22.2.0'
}

运行结果

#### setting srcipt execute  // settings.gradle 加载
#### root build.gradle execute  // root build.gradle 加载
ROOT gradle_Project lifecycle : afterEvaluate GradleDeepTest evaluate  // root project 配置完成
ROOT gradle_Project lifecycle : beforeEvaluate testlibrary evaluate  // testlibrary project 开始配置
#### library build.gradle execute  // library build.gradle 加载
ROOT gradle_Project lifecycle : afterEvaluate testlibrary evaluate  // testlibrary project 配置完成
@@@@ testlibrary lifecycle : afterEvaluate testlibrary evaluate  // testlibrary project 配置完成
ROOT gradle_Project lifecycle : beforeEvaluate app evaluate  // app project 开始配置
#### app build.gradle execute // app build.gradle 加载
ROOT gradle_Project lifecycle : afterEvaluate app evaluate  // app project 配置完成
%%%% app lifecycle : afterEvaluate app evaluate  // app project 配置完成
==ROOT== gradle Build lifecycle : projectsEvaluated GradleDeepTest evaluate  // root project 配置完成
...
BUILD SUCCESSFUL in 2s
==ROOT== gradle Build lifecycle : buildFinished GradleDeepTest evaluate // root project build完成

ps:小弟不才,有哪里说的不对的,欢迎指正~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容