Gradle實(shí)戰(zhàn)讀書(shū)筆記之一 Gradle 構(gòu)建塊

Gradle的一些命令記錄:

  1. 執(zhí)行一個(gè)指定的任務(wù):
    gradle -q taskName // 如:gradle -q hello
  2. 查看所有任務(wù)
    gradle -q tasks
  3. 執(zhí)行多個(gè)任務(wù)
    gradle hello hello2
  4. 排除任務(wù)的執(zhí)行 -x:
    gradle hello -x hello2 // 排除hello2執(zhí)行

org.gradle.api.Project 項(xiàng)目

在Gradle中,一個(gè)項(xiàng)目(project)表示一個(gè)正在構(gòu)建的組件(如:一個(gè)jar文件,android中,一個(gè)aar文件);
Gradle的build.gradle文件相當(dāng)于Maven的pom.xml,每個(gè)build.gradle文件在構(gòu)建啟動(dòng)后,對(duì)應(yīng)于org.gradle.api.Project的一個(gè)實(shí)例,并且能夠通過(guò)project變量使其隱式可用;

Project類(lèi)接口

一個(gè)Project可用創(chuàng)建新的task,添加依賴(lài)與配置,實(shí)際上這些都是 project接口的方法實(shí)現(xiàn)的,如下dependencies 就是調(diào)用 Project的 dependencies 方法,并傳遞閉包;

dependencies {
    provided 'com.android.support:support-annotations:25.3.1'
    provided 'com.android.support:support-v4:25.3.1'
}

構(gòu)建Project實(shí)例中,可通過(guò)代碼訪問(wèn)Gradle的所有特性,如:task創(chuàng)建、依賴(lài)管理等;當(dāng)訪問(wèn)屬性與方法時(shí),不需要使用project變量;

setDescription("myProject")  // 沒(méi)有顯示指定 project變量
println "Description of project $name: " + project.description;  // 訪問(wèn)name與description屬性;

在Android開(kāi)發(fā)中,每個(gè)module都表示一個(gè)Project,有自己獨(dú)立的 build.gradle腳本;

org.gradle.api.Task 任務(wù)

任務(wù)task對(duì)應(yīng)的是 org.gradle.api.Task接口;任務(wù)的一些重要功能有:任務(wù)動(dòng)作(task action)和任務(wù)依賴(lài)(task dependency);
任務(wù)動(dòng)作:定義了一個(gè)當(dāng)任務(wù)執(zhí)行時(shí)最小的工作單元;
任務(wù)依賴(lài):有些時(shí)候,某個(gè)任務(wù)的運(yùn)行之前需要其他Task先運(yùn)行;

Task接口

屬性

每個(gè)ProjectTask實(shí)例都提供可通過(guò) getter與setter方法訪問(wèn)的屬性,如:項(xiàng)目名,任務(wù)名等;
通常,我們需要自定義一些自己的屬性;比如:Android的編譯版本,minSdk等等;這就用到擴(kuò)展屬性;

擴(kuò)展屬性
在內(nèi)部,這些屬性以鍵值對(duì)的形式存儲(chǔ),添加屬性,使用 ext命名空間,在定義擴(kuò)展屬性的時(shí)候,需要使用到 ext 命名空間前綴,如下 :

// 此擴(kuò)展屬性信息,定義在一個(gè)單獨(dú)的gradle文件中,使用的使用
// 在項(xiàng)目的根build.gralde文件中 apply from: "文件名"來(lái)引用;

// gradle配置信息
ext {
    COMPILE_SDK_VERSION = 25           // 25 23
    BUILD_TOOLS_VERSION = "25.0.2"   // 25.0.2 23.0.2

    MIN_SDK_VERSION = 14
    TARGET_SDK_VERSION = 23     // 這個(gè)千萬(wàn)不能改

    // support依賴(lài)支持包
    compile_support = [
            design      : "com.android.support:design:25.3.1",
            annotations : "com.android.support:support-annotations:25.3.1",
            appcompat   : "com.android.support:appcompat-v7:25.3.1",
            recyclerview: "com.android.support:recyclerview-v7:25.3.1",
    ]

    // 常用庫(kù)包
    compile_common = [
            gson: "com.google.code.gson:gson:2.7"
    ]
}

使用的時(shí)候,可省略ext前綴,如下:

android {
    compileSdkVersion COMPILE_SDK_VERSION
    buildToolsVersion BUILD_TOOLS_VERSION
    ....

Gradle屬性
Gradle屬性可通過(guò)gradle.properties文件中聲明直接添加到項(xiàng)目中,此文件位于 <USER_HOME>/.gradle.properties目錄 或 項(xiàng)目的根目錄下,這些屬性可以通過(guò)項(xiàng)目實(shí)例訪問(wèn);
如果一個(gè)project有多個(gè)module,也只能有一份 gradle.properties文件;

project下的gradle屬性文件

比如,在properties文件聲明了(最好在本項(xiàng)目的文件做實(shí)驗(yàn),不動(dòng)全局的,見(jiàn)下解釋):
myname=zhaoyubetter
通過(guò)直接引用名字來(lái)訪問(wèn)
println(myname) // 來(lái)進(jìn)行訪問(wèn);

注意:在android studio 中,android視圖中,顯示的gradle.properties文件是全局的,與操作系統(tǒng)的是同一個(gè)文件,如下:

gradle.properties全局文件

項(xiàng)目中的 gradle.properties文件需要切換到 project files 視圖,才能看到,如下:

項(xiàng)目的gradle.properties文件

使用Task

默認(rèn)情況下,每個(gè)新創(chuàng)建的Task都是org.gradle.api.DefaultTask類(lèi)型的;

聲明task動(dòng)作
動(dòng)作(action)就是在task中合適的地方放置構(gòu)建邏輯,Task接口提供2個(gè)相關(guān)的方法來(lái)聲明task動(dòng)作:doFirst(Closure) (可使用 >>)與 doLast(Closure) (可使用<<左移);

def curVersion = '1.0.0-SNAPSHOT'
task printVersion {
    doLast {
        println ">>>>>>>>>>>>>>>>>>verson:${curVersion}"
    }
}

// or 寫(xiě)成
task printVersion << {
     println ">>>>>>>>>>>>>>>>>>verson:${curVersion}"
}

通過(guò) gradle printVersion 可執(zhí)行此任務(wù);

訪問(wèn)DefaultTask屬性

  • description屬性:描述任務(wù)的作用;
  • group屬性: 用于定義task的邏輯分組;
    創(chuàng)建Task時(shí),可傳遞,作為參數(shù):
def version = '1.0.0-SNAPSHOT'
task printVersion(group: 'versioning', description: 'Print project version') {
    doFirst {
        println ">>>>> before reading the version"
    }
    doLast {
        println ">>>>> version: $version"
    }
}

// 或者
task printVersion() {
    group = 'versioning'
    description =  'Print project version'

定義Task依賴(lài)

dependsOn方法,允許聲明依賴(lài)一個(gè)或多個(gè)task,通過(guò)task依賴(lài)關(guān)系圖來(lái)建模完整的task生命周期;

def group = 'myGroup'
task first << { println ">>>> first" }
task second << { println ">>>> second" }
// 依賴(lài)多個(gè)任務(wù)
task printVersion(group: group, dependsOn: [second, first]) << {
    println ">>>> the version : $version"
}
task third << { println ">>> third" }
third.dependsOn('printVersion')  // 聲明依賴(lài)時(shí),按名稱(chēng)引用task

輸入:gradle -q third, 輸出如下:

依賴(lài)輸出,順序是亂的

task 依賴(lài)的執(zhí)行順序
Gradle不能保證依賴(lài)的執(zhí)行順序,dependsOn只是定義了所依賴(lài)的task需要先執(zhí)行;在gradle中,執(zhí)行順序是由task的輸入/輸出規(guī)范自動(dòng)確定的;

終結(jié)器Task

使用 task 的finalizedBy來(lái)使用一個(gè)特定的終結(jié)器:

task first << { println ">>>> first" }
task second << { println ">>>> second" }
first.finalizedBy second        // 執(zhí)行 gradle -q first 時(shí),會(huì)觸發(fā) task second

添加任意代碼,設(shè)置版本信息

gradle就是groovy,所以可以加入一些groovy代碼,如下:

  // 版本
  class ProjectVersion {
    int major
    int minor
    int min

    boolean release

    ProjectVersion(int major, int minor, int min) {
        this.major = major
        this.minor = minor
        this.min = min
        this.release = false
    }

    ProjectVersion(int major, int minor, int min, boolean release) {
        this.major = major
        this.minor = minor
        this.min = min
        this.release = release
    }


    @Override
    public String toString() {
        return "$major.$minor.$min${release ? '' : '-SNAPSHOT'}"
    }
}

def version = new ProjectVersion(1, 0, 0)
task printVersion << {
    println(" >>>>>> the verson is ${version}")
}

添加task配置塊

task配置塊會(huì)在task動(dòng)作之前被執(zhí)行;

  1. 新建配置文件version.properties如下:
major=1
minor=0
min=0
release=false
  1. 添加Gradle配置 loadVersion(沒(méi)有 <<>> 運(yùn)算符) ,并讀取 properties文件
ext.versionFile = file('version.properties')
// loadVersion任務(wù),沒(méi)有 << or >> 運(yùn)算符,Gradle稱(chēng)之為 task配置
task loadVersion {
    project.version = readVersion()
}

ProjectVersion readVersion() {
    println(">>>>> reading the version file")
    if(!versionFile.exists()) {
        throw new RuntimeException(">>> Required version file does not exist: ${versionFile.canonicalPath}")
    }

    Properties prop = new Properties()
    versionFile.withInputStream { stream ->
        prop.load(stream)
    }

    new ProjectVersion(prop.major.toInteger(), prop.minor.toInteger(), prop.min.toInteger(), prop.release.toBoolean())
}

task printVersion << {
    println(" >>>>>> the verson is ${version}")     // 訪問(wèn)project的屬性version
}

Gradle構(gòu)建生命周期階段
執(zhí)行g(shù)radle構(gòu)建,會(huì)運(yùn)行三個(gè)不同的生命周期階段:初始化、配置和執(zhí)行;
如下圖:

Gradle構(gòu)建生命周期的構(gòu)建階段的順序
  1. 初始化階段:gradle為項(xiàng)目創(chuàng)建一個(gè)Project實(shí)例;
  2. 配置階段:Gradle構(gòu)造一個(gè)模型來(lái)表示任務(wù),此階段用來(lái)為項(xiàng)目或指定的task設(shè)置所需的配置;項(xiàng)目的每一次構(gòu)建的任何配置代碼都可以被執(zhí)行,即使只執(zhí)行 gradle tasks;
  3. 執(zhí)行階段:執(zhí)行task動(dòng)作;執(zhí)行的順序由他們的依賴(lài)決定;如果任務(wù)被認(rèn)為沒(méi)有修改過(guò),將跳過(guò);

聲明task的inputs和outputs

Gradle通過(guò)比較2個(gè)構(gòu)建task的inputs和outputs來(lái)決定是否是最新的;當(dāng) inputs和outputs不同時(shí),task才運(yùn)行,否則跳過(guò);
輸入可以是一個(gè)目錄,文件或?qū)傩缘龋粋€(gè)task的輸出是通過(guò)一個(gè)目錄或1~n個(gè)文件來(lái)定義的;
inputs與outputs在DefaultTask類(lèi)中,被定義為屬性或者直接子類(lèi)來(lái)表示;

DefaultTask類(lèi)定義的task的inputs和outputs

創(chuàng)建Task用來(lái)發(fā)布產(chǎn)品版本,并自動(dòng)修改配置文件

// 產(chǎn)品發(fā)布版本
task makeReleaseVersion(group: 'versioning', description: 'Makes project a reelase version.') << {
    version.release = true
    // ant task 的propertyfile 提供便利的方式修改屬性文件 (version.properties)
    ant.propertyfile(file: versionFile) {
        entry(key: 'release', type: 'string', operation: '=', value: 'true')
    }
}

通過(guò)運(yùn)行 :gralde makeReleaseVersion 可以發(fā)現(xiàn)配置文件,確實(shí)修改了;

但Gradle并不知道 我們的 是發(fā)布版本,為了解決,需要聲明它的inputs與outputs

task makeReleaseVersion(group: 'versioning', description: 'Makes project a reelase version.') {
    // 在配置階段聲明 inputs/outputs
    inputs.property('release', version.release)   // 聲明版本的release屬性作為輸入
    outputs.file versionFile                      // 由于版本文件被修改,所以他被聲明最為輸出文件屬性

    doLast {    // 閉包為動(dòng)作代碼
        version.release = true
        ant.propertyfile(file: versionFile) {
            entry(key: 'release', type: 'string', operation: '=', value: 'true')
        }
        println(">>>>>> makeReleaseVersion")
    }
}

task的inputs和outputs是在配置階段執(zhí)行的用來(lái)連接task依賴(lài),需要在配置塊中被定義,而且需要確保賦給 inputs和outputs的值在配置階段是可訪問(wèn)的;
如果需要編程獲得輸出,可通過(guò) TaskOutputs上的upToDateWhen(Closure)方法實(shí)現(xiàn),此方法是在執(zhí)行階段執(zhí)行的;如果閉包返回true,則認(rèn)為該task時(shí)最新的;

如果2次執(zhí)行 gradle makeReleaseVersion 后一次gradle知道項(xiàng)目版本被設(shè)置為發(fā)布版本了,并自動(dòng)跳過(guò)第二次執(zhí)行;

UP-TO-DATE

使用自定義Task

自定義Task包含2個(gè)組件:自定義的task類(lèi)(封裝邏輯行為)與真實(shí)的task(提供用于配置行為的task類(lèi)所暴露的屬性值);gradle稱(chēng)之為增強(qiáng)型task;
可維護(hù)性是編寫(xiě)自定義task類(lèi)的優(yōu)勢(shì)之一,畢竟操作的是一個(gè)實(shí)際的類(lèi);

編寫(xiě)自定義Task

繼承自 DefaultTask,使用注解表示輸入輸出;

class ReleaseVersionTask extends DefaultTask {

    @Input Boolean release      // 注解聲明輸入屬性 release
    @OutputFile File destFile   // 定義輸出文件

    ReleaseVersionTask() {
        group = 'versioning'
        description = 'Makes project a realase version.'
    }

 // 使用注解聲明將被執(zhí)行的方法 ,
 // 加上這個(gè)action的作用是當(dāng)執(zhí)行這個(gè)task的時(shí)候會(huì)自動(dòng)執(zhí)行這個(gè)方法
    @TaskAction    
    void start() {
        project.version.release = true
        ant.propertyfile(file: versionFile) {
            entry(key: 'release', type: 'string', operation: '=', value: 'true')
        }
    }
}

使用自定義Task
在構(gòu)建腳本中,創(chuàng)建一個(gè)ReleaseVersionTask類(lèi)型的task,并且通過(guò)它的屬性賦值來(lái)設(shè)置輸入和輸出,如:

// 增強(qiáng)型task暴露的屬性單獨(dú)賦值
task makeReleaseVersion2(type: ReleaseVersionTask) {
    release = version.relase
    destFile = versionFile
}

task規(guī)則

某些時(shí)候,編寫(xiě)的task可能做著相同的事情,如下版本的管理task任務(wù):

// 主版本+1
task incrementMajorVersion(group: 'versioning', description: 'Increments project major version.') << {
    String currentVersion = version.toString()
    ++version.major
    String newVersion = version.toString()
    logger.info "Incrementing major project version: $currentVersion -> $newVersion"

    ant.propertyfile(file: versionFile) {
        entry(key: 'major', type: 'int', operation: '+', value: 1)
    }
}

// 次版本+1
task incrementMinorVersion(group: 'versioning', description: 'Increments project major version.') << {
    String currentVersion = version.toString()
    ++version.minor
    String newVersion = version.toString()
    logger.info "Incrementing minor project version: $currentVersion -> $newVersion"

    ant.propertyfile(file: versionFile) {
        entry(key: 'minor', type: 'int', operation: '+', value: 1)
    }
}

分別運(yùn)行命令:gradle incrementMinorVersion -i ,可以看到輸出;
但上面的是否可以改進(jìn)呢?

task規(guī)則的命名模式

根據(jù)task名稱(chēng)模式執(zhí)行特定的邏輯,模式由2部分組成:
task名稱(chēng)的靜態(tài)部分 與 一個(gè)占位符,他們聯(lián)合起來(lái)組成一個(gè)命令;
如上面的,像:increment<Classifier>Version

Gralde的一些核心插件,利用了task規(guī)則,如:clean<TaskName>;

聲明task規(guī)則

首先需要獲得對(duì)TaskContainer的引用,然后可調(diào)用其addRule(String, Closure) 方法,通過(guò)project的getTasks()方法獲取 TaskContainer引用;
使用task規(guī)則,實(shí)現(xiàn)上線動(dòng)作:

// 使用 taskContainer 添加規(guī)則
tasks.addRule("Pattern: increment<Classifier>Version - Increments the project version.") {
    String taskName ->
        if (taskName.startsWith('increment') && taskName.endsWith('Version')) {
            task(taskName) << {  // 符合命名模式的task動(dòng)態(tài)添加doLast方法
                String classifier = (taskName - 'increment' - 'Version'.toLowerCase())  // 提取
                String currentVersion = version.toString()

                switch (classifier) {
                    case 'major': ++version.major
                        break
                    case 'minor': ++version.minor
                        break
                    default: throw new GradleException("Invalid version type '$classifier', Allowed " +
                            " types: ['Major','Minor']")
                }

                String newVersion = version.toString()
                logger.info "Incrementing $classifier project version: $currentVersion -> $newVersion"
                ant.propertyfile(file: versionFile) {
                    entry(key: classifier, type: 'int', operation: '+', value: 1)
                }
            }
        }
}

通過(guò)運(yùn)行gradle tasks會(huì)列出一個(gè)具體的tasks組rules:

tasks Rules 組

task 規(guī)則不能分組,其顯示在 Rules組下;

我們運(yùn)行命令:gradle incrementMajorVersion -i,輸出如下:

執(zhí)行task的分組任務(wù)的輸出

在buildSrc目錄下構(gòu)建代碼

可以將構(gòu)建腳本的一些groovy類(lèi):如上面的ProjectVersion和自定義的task ReleaseVersionTask,這些適合移動(dòng)到項(xiàng)目的 buildSrc目錄下;
Gradle在buildSrc目錄下使源文件結(jié)構(gòu)標(biāo)準(zhǔn)化,Java代碼在 src/main/java,Groovy代碼放在src/main/groovy目錄下;這些目錄下代碼會(huì)自動(dòng)編譯,并添加到Gradle構(gòu)建腳本的classpath中;

Android Studio 中在項(xiàng)目的根目錄,建立buildSrc文件夾,并sync一下工程,然后新增文件夾路徑 src/main/groovy/包名, 就可以創(chuàng)建groovy腳本了;

buildSrc項(xiàng)目出來(lái)了

在當(dāng)前 module下對(duì)應(yīng)的 build.gradle文件,直接引入 buildSrc 下的類(lèi),這樣Task定義與build.gradle分離了;

import com.better.ProjectVersion2
import com.better.ReleaseVersionTask2

總結(jié):

了解一下Task的工作細(xì)節(jié)、自定義Task,也了解一些Task配置與Task動(dòng)作之間的區(qū)別;最后我們?cè)赽uildSrc中,實(shí)現(xiàn)自定義Task的分離,將task獨(dú)立出來(lái);

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 這篇文章講給大家?guī)?lái)gradle打包系列中的高級(jí)用法-自己動(dòng)手編寫(xiě)gradle插件。我們平常在做安卓開(kāi)發(fā)時(shí),都會(huì)在...
    呆萌狗和求疵喵閱讀 16,021評(píng)論 22 80
  • 本篇主要是個(gè)人學(xué)習(xí)gradle的筆記總結(jié) 一.開(kāi)始之前 1. 為什么學(xué)習(xí)Gradle 采用DSL(Doma...
    zyq_neuq閱讀 1,529評(píng)論 2 12
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,937評(píng)論 18 139
  • 前一段時(shí)間看到不少人在技術(shù)論壇里問(wèn)「剛學(xué) Android 不久,對(duì) Gradle 不懂,看了很多資料依然一知半解」...
    f9dd77add98e閱讀 3,536評(píng)論 1 8
  • 前言 為什么需要學(xué)Gradle? Gradle 是 Android 現(xiàn)在主流的編譯工具,雖然在Gradle 出現(xiàn)之...
    真笨笨魚(yú)閱讀 1,514評(píng)論 0 0