初識Gradle

前言

Gradle是Android工程師的進階必備,最近我也開始慢慢了解Gradle。

本文不會花太多的篇幅介紹Groovy語法,主要是介紹Gradle的基本概念,和一些常見的用法。

先給大家安利一下官方文檔吧Gradle User Manual,如果英語不錯的話我感覺看官方文檔可能更好,因爲本文的主要內容也來自官方文檔。

Groovy

基本概念

Groovy是一種動態語言。這種語言比較有特點,它和Java一樣,也運行於Java虛擬機中。你可以認爲Groovy擴展了Java語言

比如,Groovy對自己的定義就是:Groovy是在 java平臺上的、 具有像Python, Ruby 和 Smalltalk 語言特性的靈活動態語言, Groovy保證了這些特性像 Java語法一樣被 Java開發者使用。

實際上,由於Groovy Code在真正執行的時候已經變成了Java字節碼,所以JVM根本不知道自己運行的是Groovy代碼。

閉包

Groovy的基本語法就不介紹了,可以參看任玉剛Gradle從入門到實戰 - Groovy基礎,這裏簡單聊一下閉包。

官方定義:

A closure in Groovy is an open, anonymous, block of code that can take arguments, return a value and be assigned to a variable.

Groovy中的閉包是一段開放的、匿名的代碼塊,並且可以帶參數,返回值,也可以賦值給一個變量。

簡而言之,閉包就是一段代碼塊

看如下代碼:

task hello {
    doLast {
        println 'Hello, My First task!
    }
}

以上代碼是簡寫,省略了圓括號,其實應該是:

task hello {
    doLast ({
        println 'Hello, My First task!
    })
}

這裏的閉包就是;

//閉包是一段代碼,所以需要用大括號括起來
{
        println 'Hello, My First task!
}

如上代碼的意思是定義了一種task叫hello(task是什麼,下文會講到),doLast就是把一個閉包對象傳遞給了這個task,在task的最後階段會執行這個閉包中的代碼println 'Hello, My First task!

有了圓括號,你會知道doLast只是把一個Closure對象傳了進去。很明顯,它不代表這段腳本解析到doLast的時候就會調用println 'Hello, My First task! 。但是把圓括號去掉後,就感覺好像println 'Hello, My First task!立即就會被調用一樣!

Gradle

Gradle簡介

Gradle是一個基於JVM的構建工具,支持傳遞性依賴管理,是一款通用靈活的構建工具。面向Java應用爲主,當然也支持Groovy、Kotlin、Scala等等。

Project

Gradle中,每一個待編譯的工程都叫一個Project。

先來看我的一個Android工程結構:
這裏寫圖片描述

我的Android工程叫AndroidStudy,它是一個Project。我的項目中還有兩個module,app和buildsrc,每個module下都有一個build.gradle文件。這裏的每一個module對於Gradle來說也是一個Project,而build.gradle文件就是該Project的編譯腳本

所以這裏有一個主Project–AndroidStudy和兩個子Project,app和buildsrc

在命令行中輸入:

gradle projects

查看工程中的Project,得到如下結果:

Root project ‘AndroidStudy’
+— Project ‘:app’
— Project ‘:buildsrc’

我們在主工程下,都有一個setting.gradle文件,它的作用是什麼呢,它就是告訴Gradle構建系統,這個主工程一共有多少個子工程需要編譯。例如,AndroidStudy項目下的setting.gradle內容是這樣的:

include ':app', ':buildsrc'

Task

每一個Project在構建的時候都包含一系列的Task。比如一個Android APK的編譯可能包含:Java源碼編譯Task、資源編譯Task、JNI編譯Task、lint檢查Task、打包生成APK的Task、簽名Task等。

我們可以在命令行輸入:

gradle tasks

去查看項目下有多少Task,得到的結果就像這樣:

Android tasks
-------------
androidDependencies - Displays the Android dependencies of the project.
signingReport - Displays the signing info for each variant.
sourceSets - Prints out all the source sets defined in this project.

Build tasks
-----------
assemble - Assembles all variants of all applications and secondary packages.
assembleAndroidTest - Assembles all the Test applications.
assembleDebug - Assembles all Debug builds.
assembleRelease - Assembles all Release builds.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
classes - Assembles main classes.
clean - Deletes the build directory.
cleanBuildCache - Deletes the build cache directory.
compileDebugAndroidTestSources
compileDebugSources
compileDebugUnitTestSources
compileReleaseSources
compileReleaseUnitTestSources
jar - Assembles a jar archive containing the main classes.
mockableAndroidJar - Creates a version of android.jar that's suitable for unit tests.
testClasses - Assembles test classes.

Build Setup tasks
-----------------
init - Initializes a new Gradle build.
wrapper - Generates Gradle wrapper files.

CreateActivity tasks
--------------------
createActivity
……

顯然Task還是有分組的。你當然也可以點擊AS右上角的按鈕來查看Task:
這裏寫圖片描述

我們常用的Task有哪些呢,比如clean,build。我們執行對應Task,其實只要在命令中進行輸入:

gradle clean
gradle build

和你點擊AS上對應的按鈕是一樣的效果。

自定義Task

例1:

task originalInputs {
    doLast {
        file('inputs').mkdir()
        file('inputs/1.txt').text = "Content for file 1."
        file('inputs/2.txt').text = "Content for file 2."
        file('inputs/3.txt').text = "Content for file 3."
    }
}

這裏是一個自定義Task,叫originalInputs,在這個Task執行的最後,會執行一些文件創建操作。

  • 一個Task包含若干Action。所以,Task有doFirst和doLast兩個函數,用於添加需要最先執行的Action和需要和需要最後執行的Action。Action就是一個閉包

例2:

task copyTaskWithPatterns(type: Copy) {
    from 'A'
    into 'B'
    include '**/*.html'
    include '**/*.jsp'
    exclude { details ->
        details.file.name.endsWith('.html') &&
                details.file.text.contains('staging')
    }
}

這裏的自定義Task叫copyTaskWithPatterns,我們可以理解成創建了一個Copy Task的對象,並指定了一些參數。這裏的操作就是,從A文件拷貝一些東西到B文件,並且對拷貝的內容進行了過濾。

  • Task創建的時候可以指定Type,通過type:名字的形式。這是什麼意思呢?其實就是告訴Gradle,這個新建的Task對象會從哪個基類Task派生。比如,Gradle本身提供了一些通用的Task,最常見的有Copy Task。Copy是Gradle中的一個類。當執行task copyTaskWithPatterns(type: Copy)的時候,創建的Task就是一個Copy Task。

例3:

class HelloTask extends DefaultTask {
    String nameOfPerson = "David"

    @TaskAction
    void hello() {
        println "Hello, $nameOfPerson !"
    }
}

如上,我們也可以在.java或者.groovy文件中聲明一個Task,但是我們並不能直接調用這個Task。還是要在build.gradle中創建它的一個對象:

task helloMike(type: com.example.tsnt.task.HelloTask) {
    nameOfPerson = "Mike"
}

最後執行helloMike這個Task,控制檯會輸出:

Hello, Mike !

Plugin

一個Project到底包含多少個Task,其實是由編譯腳本指定的插件決定。插件是什麼呢?插件就是用來定義Task,並具體執行這些Task的東西。

Gradle是一個框架,作爲框架,它負責定義流程和規則。而具體的編譯工作則是通過插件的方式來完成的。比如編譯Java有Java插件,編譯Groovy有Groovy插件,編譯Android APP有Android APP插件,編譯Android Library有Android Library插件。

加載插件就像這樣:

apply plugin: 'com.android.application'

或者:

apply plugin: 'groovy'

再或者:

apply plugin: 'java'

自定義Plugin

首先需要實現Plugin接口,然後重寫apply()方法,如下:

class FirstPlugin implements Plugin<Project> {
    @Override
    void apply(Project project) {
        System.out.println("====================");
        System.out.println("Hello, FirstPlugin!");
        System.out.println("====================");

        project.tasks.create('ballTask') {
            println("ballTask in configuration!")
            doLast {
                println 'ballTask in execution!'
            }
        }
    }
}

apply()會在我們加載插件的時候被調用,傳入的參數project就代表加載該插件的Project

這裏我自定義了一個叫FirstPlugin的插件,我在apply()裏做的操作很簡單,打印了3行文字,然後動態地創建了一個Task叫ballTask

我剛纔提到傳入的參數project就代表加載該插件的Project,那我們就能拿到這個Project對象中的屬性。

再舉個例子:

// 首先我創建了一個類GreetingPluginExtension,定義了兩個屬性。
class GreetingPluginExtension {
    String message
    String greeter
}

// 然後我創建了一個插件GreetingPlugin
class GreetingPlugin implements Plugin<Project> {
    void apply(Project project) {
        // 我拿到當前Project中的greeting,創建一個GreetingPluginExtension對象extension,用來保存greeting中的值
        def extension = project.extensions.create('greeting', GreetingPluginExtension)
        // 然後我動態地去創建一個叫hello的Task
        project.task('hello') {
            doLast {
                // 在Task執行的最後階段,去打印extension中保存的greeting中取得的值
                println "${extension.message} from ${extension.greeter}"
            }
        }
    }
}

// 最後來看一下我在build.gradle中定義的greeting
greeting {
    message = 'Hi'
    greeter = 'Gradle'
}

然後執行hello這個Task,會發現控制檯輸出:

Hi from Gradle

應該和你腦海中的結果一樣把。

Gradle的生命週期

一共有三個階段:

初始化階段:Gradle構建系統會根據setting.gradle中的內容,知道有哪些Project會需要編譯,並且爲每個Project創建一個Project對象。

配置階段:在這個階段,Gradle構建系統會執行每個加入編譯的Project下的build.gradle腳本。

執行階段:根據你所要執行的Task的依賴和參數,Gradle構建系統就會將這個Task鏈上的所有Task全部按依賴順序執行一遍。

如下是我的一個gradle腳本中的部分內容,幫助你理解Gradle的生命週期:

println 'This is executed during the configuration phase.'

task taskInConfiguration {
    println 'This is also executed during the configuration phase.'
}

task taskInExecution {
    doLast {
        println 'This is executed during the execution phase.'
    }
}

task taskInBoth {
    // 在運行階段, 最先執行
    doFirst {
        println 'This is executed first during the execution phase.'
    }

    // 在運行階段, 最後執行
    doLast {
        println 'This is executed last during the execution phase.'
    }

    // 配置階段執行
    println 'This is executed during the configuration phase as well.'
}

我去執行taskInBoth,最後執行結果如下:

Configure project :buildsrc
This is executed during the configuration phase.
This is also executed during the configuration phase.
This is executed during the configuration phase as well.
Task :buildsrc:taskInBoth
This is executed first during the execution phase.
This is executed last during the execution phase.

提示

  • 可以去gradle目錄下的samples目錄下查看官方給出的Task和Plugin的例子。

  • 定義Plugin或者Task可以選擇直接用Java,也可以選擇用Groovy,記得Groovy是Java的擴展

  • 文中所舉的例子,可以在AndroidStudy中看到。

參考

  1. Gradle User Manual
  2. Gradle API
  3. 深入理解Android之Gradle
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章