天天看点

4gradle基础概念gradle基础概念gradle生命周期常见task插入task总结Ref

gradle基础概念

本文以前文写的Posdevice和Version2Asset这2个工程为例说说gradle基础概念,主要介绍delegate、sb、常见task以及如何插入task。

gradle是配置型脚本,每个gradle文件的存在意义就是配置某个对象,一个gradle文件执行的时候,会配置一个对象。比如build.gradle会配置一个project对象,我们称这个build.gradle的委托对象。脚本(gradle文件)和委托对象的对照表如下:

Type of script Delegates to instance of 对应文件
Build script Project build.gradle
Init script Gradle
Settings script Settings setting.gradle

在脚本里可以用委托对象的方法或者属性,这句话怎么理解?举个例子

//root build.gradle
// Top-level build file where you can add configuration options common to all sub-projects/modules.
println "root build.gradle execute"
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.3'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }

}


subprojects{
//为每个子 Project 加载 utils.gradle 。当然,这句话必须位于subprojects之内
    apply from: rootProject.getRootDir().getAbsolutePath() + "/utils.gradle"
}



allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}
           

在上边的代码里,实际上我们可以看到脚本里的东西有3种类型,第一种是L3 println开头的语句(groovy代码结尾不需要加分号);第二种是buildscript,subprojects,allprojects这3个大括号形式的东西(后面我们会知道他们叫script block);第三种是定义了一个clean的task。

实际上buildscript、subprojects、allprojects都是Project类(org.gradle.api. Project)的方法,这就是在脚本里可以用委托对象的方法或者属性,在这里脚本就是build.gradle文件,而脚本的委托对象是一个Project对象,所以我们可以在脚本里使用Project的类或者方法。

我也挺震惊的,buildscript、subprojects、allprojects居然是方法,我之前一直以为是脚本就这么配置的,记住就行。

实际上buildscript的完全体应该是如下,buildscript接收闭包参数,而闭包就是这个{},groovy的语法规定,如果只有一个参数是闭包,那么可以省略(),所以就变成了上文的样子。实际上这段代码的意思是执行buildscript函数,跟我们执行println是类似的,都是方法执行。

buildscript ({
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.3'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }

})
           

gradle给这种类型的方法起了个新名字叫做script block,script block是gradle里的一个非常重要的概念.

A script block is a method call which takes a closure as a parameter

实际上下面这段代码也是函数调用,调用了一个task的函数,最后一个参数为闭包。

task clean(type: Delete) {
    delete rootProject.buildDir
}
           

script block delegate

我们看到build.gradle内有script block(简称sb),buildscript{。。。}和subprojects{。。。},是不是都是project来执行这2个sb呢?不对。我们知道闭包有delegate的概念,给闭包设置delegate,就能让闭包的执行主体改变。这里正是如此,buildscript内闭包的delegate是一个ScriptHandler。可以看buildscript的注释,如下所示,注意

against this project's {@link ScriptHandler}

,这就是指定delegate的意思。同理subprojects函数的闭包参数的target是当前project的子project,其实就是2个subproject.

/**
     * <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(Closure configureClosure);
           

build.gradle下的可配sb

Block Description
allprojects { } Configures this project and each of its sub-projects.
artifacts { } Configures the published artifacts for this project.
buildscript { } Configures the build script classpath for this project.
configurations { } Configures the dependency configurations for this project.
dependencies { } Configures the dependencies for this project.
repositories { } Configures the repositories for this project.
sourceSets { } Configures the source sets of this project.
subprojects { } Configures the sub-projects of this project.
publishing { } Configures the PublishingExtension added by the publishing plugin.

注意subprojects和allprojects的区别,allprojects=subprojects+root project

android-gradle-plugin

这个时候有个问题,我们发现build.gradle支持的sb下并没有android{},那为什么下边的build.gradle里有android{},原因是我们引入了com.android.application这个插件。那android内闭包的delegate是什么呢?看官方文档可知,app插件会让闭包的delegate为AppExtension。

apply plugin: 'com.android.application'

println 'app build.gradle execute'
android {
//    采用api导入的方式
    compileSdkVersion gradle.api
//    利用gradle.properties
    buildToolsVersion buildToolsVer
    defaultConfig {
        applicationId "com.fish.posdevice"
        minSdkVersion 
        targetSdkVersion 
        versionCode 
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:25.0.1'
    testCompile 'junit:junit:4.12'
    compile project(':cposdevicesdk')
}
           

所以在andorid内,我们可以用哪些sb,就看AppExtension支持什么。看到了熟悉的aapt,dex, signingConfigs

Block Description
aaptOptions { } Configures aapt options.
adbOptions { } Configures adb options.
buildTypes { } Configures build types.
compileOptions { } Configures compile options.
dataBinding { } Configures data binding options.
defaultConfig { } The default configuration, inherited by all product flavors (if any are defined).
dexOptions { } Configures dex options.
externalNativeBuild { } Configures external native build options.
jacoco { } Configures JaCoCo options.
lintOptions { } Configures lint options.
packagingOptions { } Configures packaging options.
productFlavors { } Configures product flavors.
signingConfigs { } Configures signing configs.
sourceSets { } Configures source sets.
splits { } Configures APK splits.
testOptions { } Configures test options.

如何查某个sb内支持哪些属性和方法

比如下边这段代码,我们怎么知道buildTypes里面可以配置minifyEnabled,proguardFiles,signingConfig。首先我们要看buildTypes这个方法的delegate,查官方文档可知buildTypes的delegate是BuildType,而看BuildType可知他支持minifyEnabled,proguardFiles,signingConfig等属性,所以我们才能在这里这么配置

buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        demo {
            //和debug使用同一个签名
            signingConfig signingConfigs.debug
        }
    }
           

gradle生命周期

对应module LifeCycle。这是一个有一个app,一个lib的android studio工程的gradle构建的基本流程。

x-Pro:LifeCycle fish$ ./gradlew assemble -q
setting.gradle execute begin...
setting.gradle execute finish...
root build.gradle execute begin...
root build.gradle execute end...
app build.gradle execute begin...
app build.gradle execute end...
app afterProject app
app beforeProject mylibrary
lib build.gradle execute begin...
lib build.gradle execute end...
app afterProject mylibrary
taskGraph.whenReady
。。。执行task
app buildFinished

           

常见task

  • assemble

    The task to assemble the output(s) of the project.

    生成所有产物,一般来说assemble依赖于assembleDebug、assembleDebug及assembleXXX(XXX为自己定义的type)

  • check

    The task to run all the checks.

  • build

    This task does both assemble and check.

    build依赖于assemble和check

  • clean

    This task cleans the output of the project,删掉所有构建产物

我一般常用asemble/asembleDebug/clean.

build,assemble,clean

build可切换, build variant

task依赖关系

初学gradle时,很想知道各个任务之间的依赖关系,但是发现没什么人提到这个,后来我明白,原来是android gradle插件太复杂了,随随便便一两百个任务。看task依赖关系,可以用visteg插件,比较好,能够图形化展示依赖关系

visteg插件

使用visteg插件很简单,只要在root的build.gradle下加以下代码就好了

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'cz.malohlava:visteg:1.0.0'
    }
}

apply plugin: 'cz.malohlava.visteg'
visteg {
    enabled        = true
    colouredNodes  = true
    colouredEdges  = true
    destination    = 'build/reports/visteg.dot'
    exporter       = 'dot'
    colorscheme    = 'spectral11'
    nodeShape      = 'box'
    startNodeShape = 'hexagon'
    endNodeShape   = 'doubleoctagon'
}
           

然后我们执行

./gradlew build

就会生成dot文件,位于build/reports/,这个文件里记载了依赖关系。

怎么看这个dot文件呢?需要graphviz,可以用homebrew装上graphviz

brew install graphviz

然后执行下边的命令就可以了,把dot转化为png,看png就好了。

cd build/reports/
dot -Tpng ./visteg.dot -o ./visteg.dot.png
           

此时我们得到了一个png图,一个dot文件,2者对照着看,task依赖关系就很清晰了。

visteg的github地址https://github.com/mmalohlava/gradle-visteg

常见task依赖关系

以Version2Asset为例,这是个单module工程。

  • assemble依赖于assembleDebug, assembleRelease, assembleDemo
  • assembleDebug依赖于compileDebugSources,packageDebug
  • as的build—>make project是执行compileDebugSources、compileDebugAndroidTestSources(注意assmbleDebug不会执行compileDebugAndroidTestSources)
  • as的build—>rebuild project是执行clean、compileDebugSources、compileDebugAndroidTestSources

as内build—>make project后可以在message里看到如下信息,这就是make project执行的task,看着很多,其实核心就是compileDebugSources和compileDebugAndroidTestSources。

Information:Gradle tasks [:app:generateDebugSources, :app:generateDebugAndroidTestSources, :app:mockableAndroidJar, :app:prepareDebugUnitTestDependencies, :app:compileDebugSources, :app:compileDebugAndroidTestSources, :app:compileDebugUnitTestSources]
           

而rebuild project会多执行一个clean

插入task

我们写自己的构建代码的时候常常要在原有task的基础上插入一些自己的task,如何插入呢?

在2个task之间插入

task之间的依赖关系都是通过dependson这个函数形成的。

B.dependsOn A 是把A加到B的依赖集(一个set)里去,并不是说B只依赖于A,他很可能还依赖于很多task。同时A也可能被很多人依赖,所有的task之间是一个有向图的关系。一个task可以被多次依赖,但不会多次执行。

我们为了自己的构建需求经常需要插入自己的task,如何插呢?

比如,已知clean->b,我想插一个iamc,即clean->c->b,怎么做?

代码如下

task b<<{
    println 'task  bb'
}

task c<<{
    println 'task  cc'
}



clean.dependsOn 'b'


project.afterEvaluate {
//    insert task between clean and 'b'
    clean.dependsOn(c)
    c.dependsOn(b)
}
           

其实,此时clean->c,b 但是c->b,所以b肯定先执行,而且只执行一次,所以我们可以写clean->c->b。

task的执行顺序可以看这篇文章,译文是这里

在某个task之前插入task

比如task clean->some tasks,我们不想知道some tasks是哪些task,我们只是想在clean和some tasks之间插入一个c,怎么做?可以先查到ome tasks,然后让clean-》c,c->some tasks,代码如下(local project:TaskInsert)

void copyDependsOn(Task base, Task task) {
    for (Object depend : base.dependsOn) {
        task.dependsOn(depend)
    }
}

task b<<{
    println 'task  bb'
}

task c<<{
    println 'task  cc'
}



clean.dependsOn 'b'


project.afterEvaluate {
//    insert task before clean
    copyDependsOn(clean, c)
    clean.dependsOn(c)
}
           

在某个task之前插代码

在某个task之前插代码还可以用doFirst,来操作,如下,在clean之前加代码。doFirst实际上是往一个task的actionList内加一个闭包,并且放在头部。

clean.doFirst{
    println 'clean first'
}
           

根据类型来找task,并设置依赖关系

比如下边就是找到JavaCompile类型的task,并且给这个task设置依赖关系。这样在compileTask执行前,必定会执行abc

task abc<<{
    println 'abc'
}

tasks.withType(JavaCompile) {
    compileTask -> compileTask.dependsOn abc
}
           

总结

通过这篇文章知道了,哪些sb可以写在build.gradle里,哪些sb可以写在android内部。常见的task,task之间的依赖关系,以及如何插入task。

Ref

https://docs.gradle.org/current/dsl/index.html

http://tools.android.com/tech-docs/new-build-system/user-guide

http://stackoverflow.com/questions/20375863/difference-between-make-and-build-in-android-studio

Uncle Chen的gradle系列文章,写的不错