天天看點

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系列文章,寫的不錯