Gradle 學習之 Project

本文大部份內容來自 Gradle 官方文檔,英文 OK 的同窗能夠直接看官方文檔。 Gradle 系列文章的示例代碼都放在了 zjxstar 的 GitHub 上了。html

前言

本文假設你們都已經掌握了 Gradle 構建項目的目錄結構。其中,腳本文件 settings.gradle 和 build.gradle 相當重要。Gradle 中的一系列構建操做都基於這兩個文件。java

本系列文章想和你們一塊兒學習 Gradle 的基礎知識,如:Project、Task 和 Transform 等。本文的主要內容是 Project ,其中會涉及到一些簡單的 Task,這裏不會詳細介紹 Task,你們只需知道有 Task 這個概念便可。文中的示例代碼都基於 Android Studio 建立的標準 Android 項目。android

Project 的基本概念

在 Gradle 中,最重要的兩個概念是 Project(項目) 和 Task(任務)。每一次構建都至少包含一個 Project,每個 Project 又包含了一個或多個 Task。每一個 build.gradle 文件都表明一個 Project,其實 Project 能夠理解爲在業務範圍內,被抽象出來的一個個獨立的模塊。而 Task 通常被定義在 build.gradle 中,它表示一個操做,好比:複製文件、打個 jar 包、上傳文件等。git

以 Android 項目舉例說明:github

Android項目目錄結構圖

注:Demo 中使用的 Gradle 版本爲 3.3.1 。閉包

使用 Android Studio 建立項目後,會自動生成兩個模塊,一個是以項目名命名的根模塊,另外一個是 app 模塊。兩個模塊中都有 build.gradle 文件,按照前文的說法,就是兩個 Project。其中,項目 GradleSample 被稱爲 rootProject ,項目 app 被稱爲 subProject 。在根 build.gradle 文件中默認有一個名爲 clean 的 Task,它的職責是刪除根模塊下 build 目錄。app

在初始化構建過程當中,Gradle 會基於 build 腳本文件來組裝 Project 和 Task。ide

Gradle 的構建生命週期

執行一個 Gradle 構建的最簡單形式是執行一個 Task,而一些 Task 可能會依賴於其餘 Task。Gradle 爲了管理這些 Task 會在任何一個 Task 執行前構建一個 DAG 圖(Directed Acyclic Graph,有向無環圖)。這意味着全部的 Task 都會被一個接一個地執行,並且只執行一次。那些沒有依賴的 Task 一般會被優先執行。學習

Gradle 的構建生命週期分爲三個階段:gradle

  • 初始化:Gradle 經過 settings.gradle 文件決定哪些項目參與構建,會建立 Project 實例。若是一個項目有多個模塊,而且每一個模塊都有其對應的 build.gradle 文件,那麼就會建立多個 Project 實例。
  • 配置:這個階段,構建腳本 build.gradle 會被執行,併爲每一個 Project 實例建立和配置 Task。DAG 依賴關係圖就在此階段生成。
  • 執行:該階段,Gradle 將決定哪一個任務會被執行。哪些任務被執行取決於開始此次構建的參數配置以及該 Gradle 文件的所在目錄。

Project 的生命週期

在 Gradle 構建的初始化階段,Gradle 會給每一個項目建立一個 Project 對象。那麼 Project 又具備怎樣的生命週期呢?

  • 爲構建建立一個 Settings 實例。
  • 根據 Settings 對象評估 settings.gradle 腳本(若是存在)以進行配置。
  • 使用配置後的 Settings 對象建立 Project 實例的層次結構。
  • 最後,經過對項目執行 build.gradle文件(若是存在)來評估每一個 Project。Project 是以廣度優先順序進行評估的,這樣就可使父 Project 在它的子 Project 以前完成評估。

Project 的屬性

Gradle 針對 Project 實例執行項目的構建文件以配置項目。build 腳本中的任何屬性和方法都會委託給關聯的 Project 對象。這意味着,能夠直接在腳本中使用 Project 接口上的任何屬性和方法。

例如:

defaultTasks('some-task')  // Delegates to Project.defaultTasks()
reportsDir = file('reports') // Delegates to Project.file() and the Java Plugin
複製代碼

你也能夠經過 Project 實例使用 Project 屬性,好比:

project.name // 獲取project的名字,而不是單獨使用name屬性
複製代碼

你能夠在構建腳本中按照名稱訪問這些屬性,也能夠經過 Project 的

project.property(java.lang.String) // 例如:project.property('version')
複製代碼

方法來進行訪問。

一個項目在搜索屬性時會考慮 5 個屬性範圍:

  • Project 對象自身。這個範圍裏的屬性包含 Project 實現類中定義有 getters 和 setters 方法的全部屬性。好比:project.getRootProject() 方法就對應了 rootProject 屬性。至於這些屬性的可讀寫性取決於它們是否認義 getters 或者 setters 方法了。
  • Project 的額外屬性 ( extra ) 。每一個 Project 都會維護一個額外屬性的映射,它能夠包含任意的名稱 -> 值對。定義後,此做用域的屬性是可讀寫的。好比:project.ext.prop1 = 'foo' 。
  • 經過插件被添加到 Project 中的擴展屬性 ( extensions ) 。每一個擴展名均可以做爲只讀屬性使用,其名稱與擴展名相同。好比:project.android.compileSdkVersion 。
  • 經過插件添加到 Project 中的約定屬性 ( convention ) 。插件能夠經過 Project 的 Convention 對象向 Project 中添加屬性和方法。此範圍的屬性的可讀可寫性取決於約束對象。
  • Project 中 Tasks 。可使用 Task 的名稱做爲屬性名稱來訪問任務。此範圍的屬性是隻讀的。

讀寫屬性時,Project 都是按照上述範圍的順序進行查找的,在某個範圍找到屬性後就會返回該屬性。若是沒有找到,會拋出異常。

經常使用的 Project 屬性有:

屬性名 做用
allprojects 當前項目及其全部子項目的集合
buildDir 當前項目的編譯目錄(自動生成)
默認值 porjectDir/build
defaultTasks 當前項目的默認任務的名字集
當前構建沒有提供任務名時會執行這些默認任務
group 當前項目的組名
logger 當前項目的日誌器,能夠用來在 build 文件中寫日誌
name 當前項目的名字
parent 當前項目的父項目
path 當前項目的路徑(絕對路徑)
project 當前項目的實例
rootProject 當前項目層次結構中的根項目
subprojects 當前項目的子項目集
tasks 當前項目的任務集
version 當前項目的版本號,默認值:unspecified

示例:使用 gradlew build 命令便可運行(基於第一小節圖中的 Android 項目)。

// 訪問Project的屬性
// 在app模塊的build.gradle中
println project.group // 打印組名:GradleSample
println version // 打印版本號,默認 unspecified
println project.name // 打印項目名:app
println rootProject.name // 打印根項目的名字:GradleSample
logger.quiet('Test project logger property') // 使用logger
for(String taskName : rootProject.defaultTasks) { // 獲取默認任務
    println "defaultTask: $taskName" // 打印:defaultTask: clean
}
println rootProject.ext.hello // 打印額外參數

// 在根build.gradle腳本中
defaultTasks 'clean' // 設置默認Task
ext { // 設置額外參數
    hello = 'Welcome to Gradle'
}
複製代碼

Project 的方法

一個項目在搜索方法時,也會考慮 5 個範圍:

  • Project 對象自身。
  • build.gradle 腳本文件。
  • 經過插件添加到 Project 中的擴展 ( extensions ) 。每一個擴展均可以當作參數是閉包或 Action 的方法。
  • 插件添加到項目中的約定方法 ( convention ) 。插件能夠經過項目的 Convention 對象向項目中添加屬性和方法。
  • 項目中的 Tasks 。每一個 Task 都會添加一個方法,方法名是任務名,參數是單個閉包或者 Action 。該方法使用提供的閉包爲相關任務調用 Task.configure( groovy.lang.Closure ) 方法。

經常使用的 Project 方法有:

方法名(不列出參數,部分方法有重載) 做用
afterEvaluate 能夠添加一個閉包,它會在項目完成評估後當即執行。
當執行屬於該項目的構建文件時,會通知此類監聽器。
allprojects 配置當前項目以及它的每一個子項目
apply 應用插件或腳本
beforeEvaluate 添加一個閉包,她會在項目開始評估前當即執行
configure 經過閉包配置對象集合
copy 拷貝特定文件
file 解析文件
findProperty 找特定屬性,返回它的值,若是沒有,返回null
hasProperty 判斷當前項目有沒有指定屬性
project 獲取指定項目的 project 對象
setProperty 給屬性設置值
subprojects 配置當前項目的全部子項目
task 定義一個任務

示例:使用 gradlew -q helloTask 命令運行便可。

// 根build.gradle
allprojects {
    // 給全部項目都添加一個任務:helloTask
    task helloTask {
        doLast { task ->
            println "I'm ${task.project.name}"
        }
    }
}

subprojects {
    helloTask { // 給全部子項目中的helloTask任務增長一個doFirst回調
        doFirst {
            println "I'm the task in sub project, doFirst"
        }
    }

    afterEvaluate { project ->
        println "it's after evaluate..."
        helloTask.configure {
            doLast {
                println "configure task after evaluate"
            }
        }
    }
}

// 給全部的子項目(除了app項目)的helloTask配置doLast
configure(subprojects.findAll{ it.name != 'app' }) {
    helloTask {
        doLast {
            println "i am not app module, i configure doLast"
        }
    }
}
複製代碼

輸出結果:

it's after evaluate...
I'm GradleSample
I'm the task in sub project, doFirst
I'm app
configure task after evaluate
複製代碼

總結

本文的內容很少,詳細介紹了 Gradle 中 Project 的生命週期、屬性和方法。同時,概述了 Gradle 構建項目的三個時期,並提到了 Task 。其實,Gradle 主要仍是圍繞 Task 來運轉的,後續文章咱們將學習 Task 的一系列知識。

參考資料

  1. Gradle 官方文檔
  2. Android 官網
  3. 《Android Gradle權威指南》
  4. 《Gradle for Android 中文版》
相關文章
相關標籤/搜索