建置效能對於生產力至關重要。建置完成所需時間越長,就越有可能中斷您的開發流程。建置每天執行多次,因此即使是短暫的等待時間也會累積起來。持續整合 (CI) 建置也是如此:所需時間越少,您就能越快對新問題做出反應,並且能更頻繁地進行實驗。

這一切都表示,值得投入一些時間和精力,讓您的建置盡可能快速。本節提供幾種加速建置的方法。此外,您還可以找到有關哪些因素會導致建置效能下降,以及如何避免這些因素的詳細資訊。

想要更快的 Gradle 建置嗎?在此註冊參加我們的建置快取深入研討會,了解 Develocity 如何將建置速度提升高達 90%。

檢查您的建置

在進行任何變更之前,請使用建置掃描或設定檔報告檢查您的建置。適當的建置檢查可協助您了解

  • 建置專案所需的時間

  • 建置的哪些部分速度緩慢

檢查提供了一個比較點,可以更好地了解此頁面上建議的變更所帶來的影響。

為了充分利用此頁面

  1. 檢查您的建置。

  2. 進行變更。

  3. 再次檢查您的建置。

如果變更改善了建置時間,請使其成為永久變更。如果您沒有看到改善,請移除變更並嘗試另一個變更。

更新版本

Gradle

Gradle 團隊持續改善 Gradle 建置的效能。如果您使用的是舊版 Gradle,您將錯過這項工作帶來的優勢。保持 Gradle 版本升級的風險很低,因為 Gradle 團隊確保 Gradle 次要版本之間的向後相容性。保持最新狀態也讓轉換到下一個主要版本更容易,因為您會收到早期的棄用警告。

Java

Gradle 在 Java 虛擬機器 (JVM) 上執行。Java 效能改進通常有益於 Gradle。為了獲得最佳 Gradle 效能,請使用最新版本的 Java。

外掛程式

外掛程式作者持續改善其外掛程式的效能。如果您使用的是舊版外掛程式,您將錯過這項工作帶來的優勢。尤其是 Android、Java 和 Kotlin 外掛程式可能會顯著影響建置效能。更新到這些外掛程式的最新版本以獲得效能改進。

啟用平行執行

大多數專案都包含多個子專案。通常,其中一些子專案彼此獨立;也就是說,它們不共用狀態。然而,預設情況下,Gradle 一次只執行一個任務。若要平行執行屬於不同子專案的任務,請使用 parallel 旗標

$ gradle <task> --parallel

若要預設平行執行專案任務,請將以下設定新增至專案根目錄或 Gradle 主目錄中的 gradle.properties 檔案

gradle.properties
org.gradle.parallel=true

平行建置可以顯著縮短建置時間;縮短多少取決於您的專案結構以及子專案之間有多少相依性。執行時間由單一子專案主導的建置不會有太多好處。具有許多子專案間相依性的專案也不會。但大多數多子專案建置都會看到建置時間縮短。

使用建置掃描視覺化平行處理

建置掃描為您提供任務執行的視覺化時間軸。在以下範例建置中,您可以在建置的開始和結束時看到長時間執行的任務

parallel task slow
圖 1. 平行執行中的瓶頸

調整建置配置以提早且平行執行兩個慢速任務,可將整體建置時間從 8 秒縮短到 5 秒

parallel task fast
圖 2. 最佳化的平行執行

重新啟用 Gradle Daemon

Gradle Daemon 透過以下方式縮短建置時間

  • 跨建置快取專案資訊

  • 在背景執行,因此每次 Gradle 建置都不必等待 JVM 啟動

  • 受益於 JVM 中的持續執行階段最佳化

  • 監看檔案系統以精確計算在您執行建置之前需要重建的內容

Gradle 預設啟用 Daemon,但某些建置會覆寫此偏好設定。如果您的建置停用 Daemon,則啟用 Daemon 可能會帶來顯著的效能提升。

您可以使用 daemon 旗標在建置時啟用 Daemon

$ gradle <task> --daemon

若要在舊版 Gradle 中預設啟用 Daemon,請將以下設定新增至專案根目錄或 Gradle 主目錄中的 gradle.properties 檔案

gradle.properties
org.gradle.daemon=true

在開發人員機器上,您應該會看到顯著的效能提升。在 CI 機器上,長期執行的代理程式受益於 Daemon。但短期執行的機器不會有太多好處。Daemon 在 Gradle 3.0 及更高版本中會在記憶體壓力下自動關閉,因此始終可以安全地保持 Daemon 啟用狀態。

啟用配置快取

此功能具有以下限制

  • 配置快取不支援所有核心 Gradle 外掛程式功能。完整支援正在進行中。

  • 您的建置和您相依的外掛程式可能需要變更才能滿足需求

  • IDE 匯入和同步不會使用配置快取。

您可以透過啟用配置快取來快取配置階段的結果。當跨建置的建置配置輸入保持不變時,配置快取允許 Gradle 完全略過配置階段。

建置配置輸入包括

  • 初始化腳本

  • 設定腳本

  • 建置腳本

  • 配置階段期間使用的系統屬性

  • 配置階段期間使用的 Gradle 屬性

  • 配置階段期間使用的環境變數

  • 使用值供應器 (例如供應器) 存取的配置檔案

  • buildSrc 輸入,包括建置配置輸入和原始碼檔案

預設情況下,Gradle 不使用配置快取。若要在建置時啟用配置快取,請使用 configuration-cache 旗標

$ gradle <task> --configuration-cache

若要預設啟用配置快取,請將以下設定新增至專案根目錄或 Gradle 主目錄中的 gradle.properties 檔案

gradle.properties
org.gradle.configuration-cache=true

如需有關配置快取的詳細資訊,請查看配置快取文件

配置快取的其他優勢

配置快取也啟用了其他優勢。啟用後,Gradle

  • 平行執行所有任務,即使是同一個子專案中的任務。

  • 快取相依性解析結果。

為自訂任務啟用增量建置

增量建置是一種 Gradle 最佳化,可略過執行先前使用相同輸入執行的任務。如果任務的輸入及其輸出自上次執行以來沒有變更,Gradle 會略過該任務。

Gradle 提供的大多數內建任務都適用於增量建置。若要使自訂任務與增量建置相容,請指定輸入和輸出

build.gradle.kts
tasks.register("processTemplatesAdHoc") {
    inputs.property("engine", TemplateEngineType.FREEMARKER)
    inputs.files(fileTree("src/templates"))
        .withPropertyName("sourceFiles")
        .withPathSensitivity(PathSensitivity.RELATIVE)
    inputs.property("templateData.name", "docs")
    inputs.property("templateData.variables", mapOf("year" to "2013"))
    outputs.dir(layout.buildDirectory.dir("genOutput2"))
        .withPropertyName("outputDir")

    doLast {
        // Process the templates here
    }
}
build.gradle
tasks.register('processTemplatesAdHoc') {
    inputs.property('engine', TemplateEngineType.FREEMARKER)
    inputs.files(fileTree('src/templates'))
        .withPropertyName('sourceFiles')
        .withPathSensitivity(PathSensitivity.RELATIVE)
    inputs.property('templateData.name', 'docs')
    inputs.property('templateData.variables', [year: '2013'])
    outputs.dir(layout.buildDirectory.dir('genOutput2'))
        .withPropertyName('outputDir')

    doLast {
        // Process the templates here
    }
}

如需有關增量建置的詳細資訊,請查看增量建置文件

使用建置掃描時間軸視覺化增量建置

查看建置掃描時間軸視圖,以識別可以從增量建置中受益的任務。這也可以幫助您了解為什麼在您期望 Gradle 略過任務時,任務會執行。

timeline
圖 3. 時間軸視圖可以協助增量建置檢查

如您在上面的建置掃描中看到的,該任務不是最新的,因為其輸入之一 ("timestamp") 已變更,導致任務必須重新執行。

依持續時間排序任務,以找出專案中最慢的任務。

啟用建置快取

建置快取是一種 Gradle 最佳化,可儲存特定輸入的任務輸出。當您稍後使用相同輸入執行相同的任務時,Gradle 會從建置快取中擷取輸出,而不是再次執行任務。預設情況下,Gradle 不使用建置快取。若要在建置時啟用建置快取,請使用 build-cache 旗標

$ gradle <task> --build-cache

若要預設啟用建置快取,請將以下設定新增至專案根目錄或 Gradle 主目錄中的 gradle.properties 檔案

gradle.properties
org.gradle.caching=true

您可以使用本機建置快取來加速單一機器上的重複建置。您也可以使用共用建置快取來加速多部機器上的重複建置。Develocity 提供了一個。共用建置快取可以縮短 CI 和開發人員建置的建置時間。

如需有關建置快取的詳細資訊,請查看建置快取文件

使用建置掃描視覺化建置快取

建置掃描可以協助您調查建置快取的有效性。在效能畫面中,"建置快取" 索引標籤會向您顯示有關以下方面的統計資訊

  • 與快取互動的任務數量

  • 使用的快取

  • 這些快取項目的傳輸和封裝/解封裝速率

cache performance
圖 4. 檢查建置的建置快取效能

"任務執行" 索引標籤顯示有關任務可快取性的詳細資訊。按一下類別以查看時間軸畫面,該畫面會醒目提示該類別的任務。

task execution cacheable
圖 5. 以任務為導向的效能視圖
timeline not cacheable
圖 6. 時間軸畫面,僅顯示「不可快取」任務

依時間軸畫面上的任務持續時間排序,以醒目提示具有巨大時間節省潛力的任務。上面的建置掃描顯示 :task1:task3 可以改進並使其可快取,並顯示 Gradle 未快取它們的原因。

為特定開發人員工作流程建立建置

最快的任務是不執行的任務。如果您可以找到略過您不需要執行的任務的方法,最終您將獲得更快的整體建置。

如果您的建置包含多個子專案,請建立任務以獨立建置這些子專案。這有助於您充分利用快取,因為對一個子專案的變更不會強制重建不相關的子專案。這也有助於縮短在不相關子專案上工作的團隊的建置時間:前端開發人員無需在每次變更前端時都建置後端子專案。文件撰寫人員即使文件與程式碼位於同一個專案中,也不需要建置前端或後端程式碼。

相反地,建立符合開發人員需求的任務。您仍然會有整個專案的單一任務圖。每個使用者群組都建議任務圖的受限視圖:將該視圖轉化為排除不必要任務的 Gradle 工作流程。

Gradle 提供多種功能來建立這些工作流程

  • 將任務指派給適當的群組

  • 建立彙總任務:沒有動作但僅相依於其他任務的任務,例如 assemble

  • 透過 gradle.taskGraph.whenReady() 等延遲配置,以便您僅在必要時執行驗證

增加堆積大小

預設情況下,Gradle 為您的建置保留 512MB 的堆積空間。這對於大多數專案來說已經足夠了。但是,某些非常大型的建置可能需要更多記憶體來容納 Gradle 的模型和快取。如果您遇到這種情況,您可以指定更大的記憶體需求。在專案根目錄或 Gradle 主目錄中的 gradle.properties 檔案中指定以下屬性

gradle.properties
org.gradle.jvmargs=-Xmx2048M

若要了解更多資訊,請查看JVM 記憶體配置文件

最佳化配置

建置生命週期章節中所述,Gradle 建置經歷 3 個階段:初始化、配置和執行。配置程式碼始終執行,無論執行的任務是什麼。因此,在配置期間執行的任何昂貴工作都會減慢每次調用速度。即使是像 gradle helpgradle tasks 這樣的簡單命令也是如此。

接下來的幾個小節介紹可以減少配置階段所花費時間的技術。

您也可以啟用配置快取以減少慢速配置階段的影響。但即使使用快取的機器,仍然偶爾會執行您的配置階段。因此,您應該使用這些技術使配置階段盡可能快速。

避免昂貴或封鎖的工作

您應該避免在配置階段執行耗時的工作。但有時它可能會以不明顯的方式潛入您的建置中。如果您在建置檔案中加密資料或呼叫遠端服務,通常很清楚。但像這樣的邏輯更常見於外掛程式,偶爾也會在自訂任務類別中找到。外掛程式的 apply() 方法或任務的建構函式中的任何昂貴工作都是一個警訊。

僅在需要時套用外掛程式

您套用至專案的每個外掛程式和腳本都會增加整體配置時間。某些外掛程式的影響比其他外掛程式更大。這並不表示您應該避免使用外掛程式,但您應該注意僅在需要時套用它們。例如,即使並非每個專案都需要外掛程式,也很容易透過 allprojects {}subprojects {} 將外掛程式套用至所有子專案。

在上面的建置掃描範例中,您可以看到根建置腳本將 script-a.gradle 腳本套用至建置內部的 3 個子專案

script a application
圖 7. 顯示 script-a.gradle 腳本套用至建置

此腳本需要 1 秒才能執行。由於它套用至 3 個子專案,因此此腳本累計延遲配置階段 3 秒。在這種情況下,有幾種方法可以減少延遲

  • 如果只有一個子專案使用該腳本,您可以從其他子專案中移除腳本應用程式。這會在每次 Gradle 調用中減少兩秒的配置延遲。

  • 如果多個子專案 (但並非全部) 使用該腳本,您可以將該腳本和所有周圍邏輯重構為位於buildSrc中的自訂外掛程式。僅將自訂外掛程式套用至相關子專案,從而減少配置延遲並避免程式碼重複。

靜態編譯任務和外掛程式

外掛程式和任務作者通常編寫 Groovy 是因為其簡潔的語法、JDK 的 API 擴充功能以及使用閉包的功能方法。但 Groovy 語法會帶來動態解釋的成本。因此,Groovy 中的方法呼叫比 Java 或 Kotlin 中的方法呼叫花費更多時間並使用更多 CPU。

當您不需要明確的動態功能時,您可以透過靜態 Groovy 編譯來降低此成本:將 @CompileStatic 註解新增至您的 Groovy 類別。如果您在方法中需要動態 Groovy,請將 @CompileDynamic 註解新增至該方法。

或者,您可以使用靜態編譯語言 (例如 Java 或 Kotlin) 撰寫外掛程式和任務。

警告: Gradle 的 Groovy DSL 嚴重依賴 Groovy 的動態功能。若要在您的外掛程式中使用靜態編譯,請切換到類似 Java 的語法。

以下範例定義了一個複製檔案的任務,而沒有動態功能

src/main/groovy/MyPlugin.groovy
project.tasks.register('copyFiles', Copy) { Task t ->
    t.into(project.layout.buildDirectory.dir('output'))
    t.from(project.configurations.getByName('compile'))
}

此範例使用所有 Gradle「網域物件容器」上可用的 register()getByName() 方法。網域物件容器包括任務、配置、相依性、擴充功能等等。某些集合 (例如 TaskContainer) 具有專用類型,其中包含額外的方法,例如create,它接受任務類型。

當您使用靜態編譯時,IDE 可以

  • 快速顯示與無法辨識的類型、屬性和方法相關的錯誤

  • 自動完成方法名稱

最佳化相依性解析

相依性解析簡化了將第三方程式庫和其他相依性整合到您的專案中。Gradle 會聯絡遠端伺服器以探索和下載相依性。您可以最佳化您參考相依性的方式,以減少對這些遠端伺服器的呼叫。

避免不必要和未使用的相依性

管理第三方程式庫及其傳遞相依性會為專案維護和建置時間增加顯著成本。

注意未使用的相依性:當第三方程式庫停止使用但未從相依性列表中移除時。這在重構期間經常發生。您可以使用Gradle Lint 外掛程式來識別未使用的相依性。

如果您僅在第三方程式庫中使用少量方法或類別,請考慮

  • 在您的專案中自行實作所需的程式碼

  • 如果程式碼是開放原始碼,則從程式庫複製所需的程式碼 (並註明出處!)

最佳化儲存庫順序

當 Gradle 解析相依性時,它會依宣告順序搜尋每個儲存庫。為了減少搜尋相依性所花費的時間,請先宣告託管最多相依性的儲存庫。這會最大限度地減少解析所有相依性所需的網路請求數量。

最大限度地減少儲存庫計數

將宣告的儲存庫數量限制為您的建置可運作的最小值。

如果您使用的是自訂儲存庫伺服器,請建立一個彙總多個儲存庫的虛擬儲存庫。然後,僅將該儲存庫新增至您的建置檔案。

最大限度地減少動態和快照版本

動態版本 (例如「2.+」) 和變更版本 (快照) 強制 Gradle 聯絡遠端儲存庫以尋找新版本。預設情況下,Gradle 每 24 小時僅檢查一次。但您可以使用以下設定以程式設計方式變更此設定

  • cacheDynamicVersionsFor

  • cacheChangingModulesFor

如果建置檔案或初始化腳本降低了這些值,Gradle 會更頻繁地查詢儲存庫。當您不需要每次建置時都取得相依性的絕對最新版本時,請考慮移除這些設定的自訂值。

使用建置掃描尋找動態和變更版本

您可以透過建置掃描找到所有具有動態版本的相依性

dependency dynamic versions
圖 8. 尋找具有動態版本的相依性

您可以使用固定版本 (例如「1.2」和「3.0.3.GA」),讓 Gradle 可以快取版本。如果您必須使用動態和變更版本,請調整快取設定以最符合您的需求。

避免在配置期間進行相依性解析

相依性解析是一個昂貴的程序,無論是在 I/O 還是計算方面。Gradle 透過快取減少所需的網路流量。但仍然有成本。Gradle 在每次建置時都會執行配置階段。如果您在配置階段觸發相依性解析,則每次建置都會支付該成本。

切換到宣告式語法

如果您評估配置檔案,您的專案會在配置期間支付相依性解析的成本。通常,任務會評估這些檔案,因為您在準備好在任務動作中使用它們之前不需要這些檔案。假設您正在進行一些偵錯,並且想要顯示組成配置的檔案。若要實作此功能,您可以插入 print 陳述式

build.gradle.kts
tasks.register<Copy>("copyFiles") {
    println(">> Compilation deps: ${configurations.compileClasspath.get().files.map { it.name }}")
    into(layout.buildDirectory.dir("output"))
    from(configurations.compileClasspath)
}
build.gradle
tasks.register('copyFiles', Copy) {
    println ">> Compilation deps: ${configurations.compileClasspath.files.name}"
    into(layout.buildDirectory.dir('output'))
    from(configurations.compileClasspath)
}

files 屬性會強制 Gradle 解析相依性。在此範例中,這會在配置階段發生。由於配置階段在每次建置時都會執行,因此所有建置現在都支付相依性解析的效能成本。您可以使用 doFirst() 動作來避免此成本

build.gradle.kts
tasks.register<Copy>("copyFiles") {
    into(layout.buildDirectory.dir("output"))
    // Store the configuration into a variable because referencing the project from the task action
    // is not compatible with the configuration cache.
    val compileClasspath: FileCollection = configurations.compileClasspath.get()
    from(compileClasspath)
    doFirst {
        println(">> Compilation deps: ${compileClasspath.files.map { it.name }}")
    }
}
build.gradle
tasks.register('copyFiles', Copy) {
    into(layout.buildDirectory.dir('output'))
    // Store the configuration into a variable because referencing the project from the task action
    // is not compatible with the configuration cache.
    FileCollection compileClasspath = configurations.compileClasspath
    from(compileClasspath)
    doFirst {
        println ">> Compilation deps: ${compileClasspath.files.name}"
    }
}

請注意,from() 宣告不會解析相依性,因為您使用的是相依性配置本身作為引數,而不是檔案。Copy 任務會在任務執行期間解析配置本身。

使用建置掃描視覺化相依性解析

建置掃描效能頁面上的「相依性解析」索引標籤顯示配置和執行階段的相依性解析時間

bad dependency resolution
圖 9. 配置時的相依性解析

建置掃描提供了識別此問題的另一種方法。您的建置應該在「專案配置」期間花費 0 秒來解析相依性。此範例顯示建置在生命週期中過早解析相依性。您也可以在「效能」頁面上找到「設定和建議」索引標籤。這會顯示在配置階段解析的相依性。

移除或改善自訂相依性解析邏輯

Gradle 允許使用者以最適合他們的方式塑模相依性解析。簡單的自訂 (例如強制使用特定版本的相依性或將一個相依性替換為另一個相依性) 對相依性解析時間沒有太大影響。更複雜的自訂 (例如下載和剖析 POM 的自訂邏輯) 會顯著減慢相依性解析速度。

使用建置掃描或設定檔報告來檢查自訂相依性解析邏輯是否對相依性解析時間產生不利影響。這可能是您自己撰寫的自訂邏輯,也可能是外掛程式的一部分。

移除緩慢或意外的相依性下載

緩慢的相依性下載可能會影響您的整體建置效能。有幾個因素可能會導致這種情況,包括網際網路連線速度慢或儲存庫伺服器過載。在建置掃描的「效能」頁面上,您會找到「網路活動」索引標籤。此索引標籤列出以下資訊,包括

  • 下載相依性所花費的時間

  • 相依性下載的傳輸速率

  • 依下載時間排序的下載清單

在以下範例中,兩個緩慢的相依性下載分別花費了 20 秒和 40 秒,並減慢了建置的整體效能

slow dependency downloads
圖 10. 識別緩慢的相依性下載

檢查下載清單中是否有意外的相依性下載。例如,您可能會看到由使用動態版本的相依性引起的下載。

透過切換到不同的儲存庫或相依性來消除這些緩慢或意外的下載。

最佳化 Java 專案

以下章節僅適用於使用 java 外掛程式或其他 JVM 語言的專案。

最佳化測試

專案通常會花費許多建置時間在測試上。這些測試可能包含單元測試和整合測試。整合測試通常需要較長的時間。建置掃描可以幫助您找出最慢的測試。然後您可以專注於加速這些測試。

tests longest
圖 11. 測試畫面,依專案顯示測試,並依持續時間排序

上述建置掃描顯示所有執行測試的專案的互動式測試報告。

Gradle 有幾種加速測試的方法

  • 平行執行測試

  • 將測試 Fork 到多個程序中

  • 停用報告

讓我們依序看看這些方法。

平行執行測試

Gradle 可以平行執行多個測試案例。若要啟用此功能,請覆寫相關 Test 任務中 maxParallelForks 的值。為了獲得最佳效能,請使用小於或等於可用 CPU 核心數的數字

build.gradle.kts
tasks.withType<Test>().configureEach {
    maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1)
}
build.gradle
tasks.withType(Test).configureEach {
    maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1
}

平行測試必須是獨立的。它們不應共用檔案或資料庫等資源。如果您的測試確實共用資源,它們可能會以隨機且不可預測的方式相互干擾。

將測試 Fork 到多個程序中

預設情況下,Gradle 會在單一 Fork 的 VM 中執行所有測試。如果有很多測試,或是一些測試消耗大量記憶體,您的測試執行時間可能會比預期的長。您可以增加堆積大小,但垃圾收集可能會減慢您的測試速度。

或者,您可以使用 forkEvery 設定在執行一定數量的測試後 Fork 一個新的測試 VM

build.gradle.kts
tasks.withType<Test>().configureEach {
    forkEvery = 100
}
build.gradle
tasks.withType(Test).configureEach {
    forkEvery = 100
}
Fork VM 是一個昂貴的操作。在此處設定太小的值會減慢測試速度。

停用報告

無論您是否要查看,Gradle 都會自動建立測試報告。報告產生會減慢整體建置速度。在以下情況下,您可能不需要報告:

  • 您只關心測試是否成功(而不是原因)

  • 您使用建置掃描,它提供的資訊比本機報告更多

若要停用測試報告,請在 Test 任務中將 reports.html.requiredreports.junitXml.required 設定為 false

build.gradle.kts
tasks.withType<Test>().configureEach {
    reports.html.required = false
    reports.junitXml.required = false
}
build.gradle
tasks.withType(Test).configureEach {
    reports.html.required = false
    reports.junitXml.required = false
}
有條件地啟用報告

您可能想要有條件地啟用報告,這樣您就不必編輯建置檔案即可查看它們。若要根據專案屬性啟用報告,請在停用報告之前檢查屬性是否存在

build.gradle.kts
tasks.withType<Test>().configureEach {
    if (!project.hasProperty("createReports")) {
        reports.html.required = false
        reports.junitXml.required = false
    }
}
build.gradle
tasks.withType(Test).configureEach {
    if (!project.hasProperty("createReports")) {
        reports.html.required = false
        reports.junitXml.required = false
    }
}

然後,在命令列中使用 `-PcreateReports` 傳遞屬性以產生報告。

$ gradle <task> -PcreateReports

或者在專案根目錄或您的 Gradle home 目錄中的 `gradle.properties` 檔案中設定屬性

gradle.properties
createReports=true

最佳化編譯器

Java 編譯器速度很快。但是,如果您要編譯數百個 Java 類別,即使是短暫的編譯時間也會累積起來。Gradle 為 Java 編譯提供了幾種最佳化方法

  • 將編譯器作為單獨的程序執行

  • 將僅限內部的依賴項切換為實作可見性

將編譯器作為單獨的程序執行

您可以使用以下組態,針對任何 JavaCompile 任務將編譯器作為單獨的程序執行

build.gradle.kts
<task>.options.isFork = true
build.gradle
<task>.options.fork = true

若要將組態套用至*所有* Java 編譯任務,您可以 configureEach java 編譯任務

build.gradle.kts
tasks.withType<JavaCompile>().configureEach {
    options.isFork = true
}
build.gradle
tasks.withType(JavaCompile).configureEach {
    options.fork = true
}

Gradle 在建置期間重複使用此程序,因此 Fork 的開銷很小。透過將記憶體密集型編譯 Fork 到單獨的程序中,我們可以最大限度地減少主 Gradle 程序中的垃圾收集。較少的垃圾收集意味著 Gradle 的基礎架構可以更快地運行,尤其是在您也使用平行建置時。

Fork 編譯很少影響小型專案的效能。但是,如果單一任務一起編譯超過一千個原始碼檔案,您應該考慮它。

將僅限內部的依賴項切換為實作可見性

只有程式庫可以定義 api 依賴項。使用 `java-library` 外掛程式在您的程式庫中定義 API 依賴項。使用 java 外掛程式的專案無法宣告 api 依賴項。

在 Gradle 3.4 之前,專案使用 compile 組態宣告依賴項。這會將所有這些依賴項暴露給下游專案。在 Gradle 3.4 及更高版本中,您可以將面向下游的 api 依賴項與僅限內部的 implementation 詳細資訊分開。實作依賴項不會洩漏到下游專案的編譯類路徑中。當實作詳細資訊變更時,Gradle 只會重新編譯 api 依賴項。

build.gradle.kts
dependencies {
   api(project("my-utils"))
   implementation("com.google.guava:guava:21.0")
}
build.gradle
dependencies {
   api project('my-utils')
   implementation 'com.google.guava:guava:21.0'
}

這可以顯著減少大型多專案建置中單一變更引起的重新編譯「連鎖反應」。

改善舊版 Gradle 版本的效能

有些專案無法輕易升級到目前的 Gradle 版本。雖然在可能的情況下,您應該始終將 Gradle 升級到最新版本,但我們認識到,對於某些特定的情況,這並不總是可行的。在這些精選案例中,請查看這些建議以最佳化舊版 Gradle。

啟用 Daemon

Gradle 3.0 及更高版本預設啟用 Daemon。如果您使用的是舊版本,則應更新到最新版本的 Gradle。如果您無法更新您的 Gradle 版本,您可以手動啟用 Daemon

使用增量編譯

Gradle 可以分析依賴項到個別類別層級,以僅重新編譯受變更影響的類別。Gradle 4.10 及更高版本預設啟用增量編譯。若要在舊版 Gradle 版本中預設啟用增量編譯,請將以下設定新增至您的 `build.gradle` 檔案

build.gradle.kts
tasks.withType<JavaCompile>().configureEach {
    options.isIncremental = true
}
build.gradle
tasks.withType(JavaCompile).configureEach {
    options.incremental = true
}

使用編譯避免

通常,更新只會變更程式碼的內部實作詳細資訊,例如方法的主體。這些更新稱為 *ABI 相容* 變更:它們對專案的二進位介面沒有影響。在 Gradle 3.4 及更高版本中,ABI 相容變更不再觸發下游專案的重新編譯。這尤其改善了具有深度依賴鏈的大型多專案建置中的建置時間。

升級到 Gradle 3.4 以上版本以受益於編譯避免。

如果您使用註解處理器,則需要明確宣告它們,編譯避免才能運作。若要瞭解更多資訊,請查看編譯避免文件

最佳化 Android 專案

此頁面上的所有內容都適用於 Android 建置,因為 Android 建置使用 Gradle。然而,Android 也為最佳化帶來了獨特的機會。如需更多資訊,請查看Android 團隊效能指南。您也可以觀看 Google IO 2017 隨附的演講