讀懂 Gradle 的 DSL

来源:https://www.cnblogs.com/sw926/archive/2019/02/12/10365370.html
-Advertisement-
Play Games

現在 Android 開發免不了要和 Gradle 打交道,所有的 Android 開發肯定都知道這麼在 中添加依賴,或者添加配置批量打包,但是真正理解這些腳本的人恐怕很少。其實 Gradle 的 可以說是一個代碼文件,熟悉 Java 的人理解起來很簡單的,之所以不願意去涉及,主要感覺沒有必要去研究 ...


現在 Android 開發免不了要和 Gradle 打交道,所有的 Android 開發肯定都知道這麼在 build.gradle 中添加依賴,或者添加配置批量打包,但是真正理解這些腳本的人恐怕很少。其實 Gradle 的 build.gradle 可以說是一個代碼文件,熟悉 Java 的人理解起來很簡單的,之所以不願意去涉及,主要感覺沒有必要去研究。要能看懂 build.gradle,除了要瞭解 Groovy 的語法,還要瞭解 Gradle 的構建流程,要研究還是要花一些時間的,所以這篇文章可以讓一個 Java 程式員在一個小時內看懂 Gradle 的腳本。

Gradle 簡單介紹

Gradle 構建由 Project 和 Task 組成,Project 保存項目的屬性,例如 name,版本號,代碼文件位置。Task 也是 Project 的一部分,但是它是可執行的任務,我們最常使用的 build 就是一個 Task,Task 可以依賴於另外一個 Task,一個 Task 在執行的時候,它依賴的 Task 會先執行。這樣,當我們 build 的時候,這個 Task 可能依賴很多的 Task,比如代碼檢查、註解處理,這樣一層層的依賴,最終通過 build Task 全部執行。

Gradle 和 Groovy

Gradle 和 Groovy 這兩個名字很容易讓人產生混淆,這裡先解釋一下,Groovy 是一門編程語言,和 Java 一樣。Gradle 和一個自動化構建工具,其他知名的構建工具還有 Maven 和 Ant。什麼自動化構建工具?用 Android 來舉例,打包一個 Apk 文件要做很多工作,代碼預處理,lint代碼檢查、處理資源、編譯 Java 文件等等,使用自動化構建工具,一個命令就可以生成 Apk 了。

Gradle 的 DSL 目前支持兩種語言的格式,Groovy 和 Kotlin,Kotlin 格式的 DSL 是在 5.0 引入的,相比 Groovy,Kotlin 使用的人數更多,也更好理解,在這兒主要介紹 Groovy 格式的 DSL。

介紹一下什麼是 DSL,DSL 是 Domain Specific Language 的縮寫,既領域專用語言。Gradle 的 DSL 專門用於配置項目的構建,不能做其他工作,而像 Java 、C/C++ 這些就屬於通用語言,可以做任何工作。

我們還要理解什麼是腳本文件。在寫代碼 Java 代碼時,程式是從 main() 函數開始執行的,只有在 main() 中調用的代碼才會執行。但是腳本文件不一樣,只要在文件內寫的代碼都會執行,Groovy 是支持腳本文件的,我們配置好 Groovy 的開發環境,新建一個文件 test.groovy,輸入以下內容:

String hello = "Hello World!"
println(hello)

println("The End")

然後運行:

groovy test.groovy

輸出結果為:

Hello World!
The End

雖然沒有 main 函數,但是裡面的代碼都執行了。很明顯,build.gradle 就是一個 Groovy 的腳本文件,裡面就是 Groovy 代碼,裡面添加的所有代碼都會運行,我們可以試驗以下,隨便打開一個 Gradle 格式的項目,在 build.gradle 最下麵添加一些 Java 代碼:

String hello = "Hello World!"
System.out.println(hello)

然後執行:

./gradlew -q # -q 是不輸出額外的信息

我們會看到輸出了 Hellow World,說明我們添加的代碼被執行了,那麼為什麼可以在 build.gradle 裡面寫 Java 代碼呢?這是因為 Groovy 是支持 Java 的語法的,在 Groovy 文件寫 Java 代碼是完全沒有問題的。

build.gradle 的執行方式

現在總結一下,build.gradle 就是一個 Groovy 格式腳本文件,裡面是 Groovy 或者 Java 代碼,構建的時候會順序執行,但是打開 build.gradle,可能還是一頭霧水,一個個字元和大括弧組成的東西到底是什麼鬼?我們來看一下最長使用的 dependencies

dependencies {
    // This dependency is found on compile classpath of this component and consumers.
    implementation 'com.google.guava:guava:26.0-jre'

    // Use JUnit test framework
    testImplementation 'junit:junit:4.12'
}

implementation 也可以這樣寫:

implementation('com.google.guava:guava:26.0-jre')

implementation 其實就是一個函數,在 Groovy 中,函數調用可以使用空格加參數的形式調用,例如

void foo(String params1, int param2) {
    println("param1 = $params1, param2 = $param2")
}

foo "aaa", 2

implementation 'com.google.guava:guava:26.0-jre' 就是調用了 implementation 函數添加了一個依賴。以此類推,dependencies 也是一個函數,在 IDEA 中,我們可以直接 Ctrl 加滑鼠左鍵點擊進去看它的聲明:

public interface Project extends Comparable<Project>, ExtensionAware, PluginAware {
    // ...
    void dependencies(Closure configureClosure);
    // ...
}

我們看到 dependenciesProject 一個方法,為什麼可以在 build.gradle 調用 Project 的方法呢,官方文檔裡面有相關的介紹。一個 Gradle 項目一般有一個 settings.gradle 文件和一個 build.gradle 文件,settings.gradle 用來配置目錄結構,子工程就是在 settings.gradle 裡面配置,Projectbuild.gradle 是一一對應的關係,Gradle 的構建流程如下:

1、生成一個 Settings 對象,執行 settings.gradle 對這個對象進行配置
2、使用 Settings 對象生成工程結構,創建 Project 對象
3、對所有 Project 執行對應的 build.gradle 進行配置

build.gradle 就是對 Project 的操作,例如,在 build.gradle 中輸入以下代碼

println "project name is ${this.name}"

輸出結果為: project name is java_demojava_demo 就是我們的 project name,我們可以認為對 this 的操作就是對 project 的操作。

Groovy 也是有語法糖的,類的屬性可以直接使用名字,例如 Project 的有兩個函數:

Object getVersion();
void setVersion(Object version);

那麼這就說明 Project 有一個 version 屬性,在 build.gradle 中我們可以這樣來使用:

version = "1.0" // 賦值,調用 setVersion()
println version // 讀取,調用 getVersion()

Project 中沒有 getter 方法的屬性是不能賦值的,例如 name,我們可以輸出 name 的值,但是 name = "demo" 是錯誤的。

所以,在 build.gradle 中的代碼就是修改 Project,方式就是修改屬性或者調用相關的方法,plugins 方法是添加插件,repositories 方法是添加代碼倉庫,

Groovy 閉包

閉包可以認為是可以執行的代碼塊,Groovy 中閉包的聲明和執行方式如下:

Closure closure = { String item ->
    println(item)
}

closure("Hello") // 執行

和 Lambda 表達式很像,但是 Groovy 的閉包可以先聲明,然後設置代理來執行,例如我們聲明一個閉包:

Closure closure = {
    sayHello()
}

這個閉包裡面執行了 sayHello() 函數,但是我們沒有在任何地方聲明這個函數,在 Java 中,這是個編譯錯誤,但是 Groovy 是允許的,完整的執行的例子如下:

Closure closure = {
    sayHello()
}
class Foo {
    void sayHello() {
        println("Hello!!!")
    }
}
def foo = new Foo()

closure.delegate = foo
closure()

輸出結果為:

Hello!!! 

我們為閉包設置了一個代理 delegate,只要這個代理有 sayHello() 方法,代碼就能執行,這就是為什麼我們查看 Project 的源碼,裡面很多函數參數類型都是 Closure,例如:

void repositories(Closure configureClosure);
void dependencies(Closure configureClosure);

repositoriesbuild.gradle 中是這樣調用的:

repositories {
    // Use jcenter for resolving your dependencies.
    // You can declare any Maven/Ivy/file repository here.
    jcenter()
}

我們通過 IDE 進入 jcenter() 的聲明,進入的是:

public interface RepositoryHandler extends ArtifactRepositoryContainer {
    // ...
}

由於沒看過源碼,我也只能猜,我猜 repositories 這個閉包的 delegate 是一個 RepositoryHandler,通過執行 RepositoryHandler 的方法,為工程添加 Repository

Plugin

來看我們使用最多的 dependencies

dependencies {
    // This dependency is found on compile classpath of this component and consumers.
    implementation 'com.google.guava:guava:26.0-jre'

    implementation('com.google.guava:guava:26.0-jre')

    // Use JUnit test framework
    testImplementation 'junit:junit:4.12'
}

在 Java 和 Android 項目中 implementation 是一定會用到的,但是一個 Gradle Basic 項目是沒有 implementation 的,實際上,在 dependencies 是不能直接添加任何依賴的。

這裡我們有說一下 Gradle 怎麼解決依賴。

Gradle 空白項目沒有編譯 Java 項目的能力,但是它能從倉庫下載依賴的庫並且配置到 Project 中。在我們編譯 Java 項目的時候,一個配置是不夠的,至少要有個測試版,正式版,兩個版本依賴的庫可能是不一樣的,兩個版本部分代碼也是不一樣的,那麼我們怎麼區分呢?在 Gradle 中,是通過 configurations,也就是配置,每個配置可以單獨的添加依賴,在編譯的時候,也就是執行某個 Task 的時候,通過讀取配置中的依賴來添加 classpath,例如:

repositories {
    mavenCentral()
}

configurations {
    test
    release
}

dependencies {
    test 'org.apache.commons:commons-lang3:3.0'
    release 'org.slf4j:slf4j-log4j12:1.7.2'
}



task buildTest {
    doLast {
        println configurations.test.name
        println configurations.test.asPath
    }
} 

執行 ./gradlew buildTest -q,輸出結果為:

test
/Users/xxx/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-lang3/3.0/8873bd0bb5cb9ee37f1b04578eb7e26fcdd44cb0/commons-lang3-3.0.jar

如果在 buildTest 這個 Task 中進行編譯工作的話,我們就可以直接讀取 configurations.test 的路徑設置為 classpath

implementation 就是通過添加了一個 implementation 配置來實現的。這個配置是通過:

plugins {
    // Apply the java plugin to add support for Java
    id 'java'

    // Apply the application plugin to add support for building an application
    id 'application'
}

添加的,我們通過 plugins 可以給 Project 添加屬性,Tasks,配置,例如我們寫一個最簡單的插件:

package com.demo

import org.gradle.api.Plugin
import org.gradle.api.Project

class DemoPlugin implements Plugin<Project> {
    void apply(Project project) {

        project.task("hello") {
            doLast {
                println "Hello World"
            }
        }

        project.configurations {
            demoCompile
        }
    }
}

這個插件為 Project 添加了一個 Task,添加了一個配置,我們將這個文件 DemoPlugin.groovy 放在項目根目錄下的 buildSrc/src/main/groovy/demo/ 下,就可以在 build.gradle 中直接使用了:

apply plugin: com.demo.DemoPlugin

buildscript

對於 buildscript,例如:

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

它的作用是為構建腳本提供依賴,例如我們在項目中使用了 Android 的 Plugin,這個 Plugin 的要從哪找下載?這就需要在 buildscript 中指定。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • MERGE 存儲引擎把一組 MyISAM 數據表當做一個邏輯單元來對待,讓我們可以同時對他們進行查詢。構成一個 MERGE 數據表結構的各成員 MyISAM 數據表必須具有完全一樣的表結構。每一個成員數據表的數據列必須按照同樣的順序定義同樣的名字和類型,索引也必須按照同樣的順序和同樣的方式定義。假設 ...
  • SELECT dbms_lob.substr(dbms_metadata.get_ddl('INDEX', INDEX_NAME))||';' from dba_indexes where owner='需要導出所有索引的用戶' refer to:https://www.cnblogs.com/lh ...
  • 當需要周期性的去執行一個方法時,我們可以先寫好方法,然後交給資料庫去完成就可以的。 步驟:首先打開SQL資料庫中SQLServer代理--》右鍵作業--》新建作業: 如果SQL Server代理被禁用了,那就郵件啟動代理即可; 或者這樣: 選擇電腦——>設備管理——>服務與應用程式——>服務——> ...
  • Linux中安裝完Oracle後,預設的 上下鍵是不能用的,安裝了 之後就能通過上下鍵翻回歷史命令了 1. 下載地址 "https://github.com/hanslub42/rlwrap/releases" 2. 安裝步驟 安裝readline 及 依賴 上傳、解壓、編譯、安裝rlwrap 配置 ...
  • 一、下載安裝 MySQL是一個關係型資料庫管理系統,由瑞典MySQL AB 公司開發,目前屬於 Oracle 旗下公司。MySQL 最流行的關係型資料庫管理系統,在 WEB 應用方面MySQL是最好的 RDBMS (Relational Database Management System,關係數據 ...
  • 禁用觸發器: 啟用觸發器: ...
  • SQLite資料庫簡介 SQLite 是一個輕量級資料庫,它是D. Richard Hipp建立的公有領域項目,在2000年發佈了第一個版本。它的設計目標是嵌入式的,而且占用資源非常低,在記憶體中只需要占用幾百kB的存儲空間,這也是Android移動設備採用SQLite資料庫的重要原因之一。 SQLi ...
  • 前言 工欲善其事,必先利其器 所以第一篇我們來說說 Flutter 環境的搭建。 筆者這邊使用的是 MAC 電腦,因此以 MAC 電腦的環境搭建為例。 Windows 或者 Linux 也是類似的操作。 Flutter 有英文版的官網和中文網,大家可以根據自己的喜好和情況進行選擇。 點擊下麵的鏈接可 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...