Android Note - 構建速度優化

NianyiYang發表於2019-03-26

前言

最近因為專案的編譯速度越來越慢,嚴重到有時候甚至接近十分鐘才能完成一次完整編譯,就決定對著官方文件對Gradle進行一番優化。優化完成後果然構建速度得到大幅提升,遂在此記錄

如何開始

由於是對照官方文件並結合實際專案進行優化,所以某些細節或者專案中沒有用到的點就簡略帶過

保持工具處於最新狀態

Android Studio 和 SDK 工具

Android Plugin for Gradle

為開發建立構建變體

即為生產環境與開發環境分開配置productFlavors

避免編譯不必要的資源

您可以僅為devproductFlavors指定一個語言資源和螢幕密度

productFlavors {
    dev {
      resConfigs "en", "xxhdpi"
    }
    ...
}
複製程式碼

為您的除錯構建停用 Crashlytics(Fabric)

一般是在debug時停用

android {
  ...
  buildTypes {
    debug {
      ext.enableCrashlytics = false
    }
}
複製程式碼

將靜態構建配置值與除錯構建結合使用

始終為進入 manifest 檔案的屬性使用靜態/硬編碼值,或者為您的除錯構建型別使用資原始檔。如果您的 manifest 檔案或應用資源中的值需要隨著每一個構建更新,Instant Run 將無法執行程式碼交換 - 它必須構建和安裝新的 APK

例如,在您每次想要執行更改時,使用動態版本程式碼、版本名稱、資源或任何其他可以更改 manifest 檔案的構建邏輯都需要一個完整的 APK 構建 - 即使實際更改僅需要一個熱交換,也是如此。如果您的構建配置需要此類動態屬性,那麼將其隔離到您的釋出構建變體中並讓值對您的除錯構建保持靜態

簡單點來說,就是如果gradle構建中使用了動態構建配置,那麼Instant Run就無法起到應有的作用,與直接構建新的APK沒有任何區別

下面是例子

int MILLIS_IN_MINUTE = 1000 * 60
int minutesSinceEpoch = System.currentTimeMillis() / MILLIS_IN_MINUTE

android {
    ...
    defaultConfig {
        versionCode 1
        versionName "1.0"
        ...
    }

    applicationVariants.all { variant ->
        if (variant.buildType.name == "release") {
            variant.mergedFlavor.versionCode = minutesSinceEpoch;
            variant.mergedFlavor.versionName = minutesSinceEpoch + "-" + variant.flavorName;
        }
    }
}
複製程式碼

使用靜態依賴項版本

build.gradle檔案中宣告依賴項時,您應當避免在結尾將版本號與加號一起使用,例如com.android.tools.build:gradle:2.+ 使用動態版本號可能導致意外版本更新和難以解析版本差異,並因 Gradle 檢查有無更新而減慢構建速度。您應改為使用靜態/硬編碼版本號

這個大家都懂,就不多說了

啟用離線模式

如果您的網路連線速度比較慢,那麼在 Gradle 嘗試使用網路資源解析依賴項時,您的構建時間可能會延長。您可以指示 Gradle 僅使用它已經快取到本地的工件來避免使用網路資源

這個也是很常見的加快構建速度的方式,尤其是在國內,使用離線模式後構建速度可以大大提升。但是離線模式在每次引用新的依賴時會找不到依賴,所以新專案的話,還是能不用就不要用吧

啟用按需配置

為了讓 Gradle 準確瞭解如何構建您的應用,構建系統會在每個構建前在專案中配置所有模組以及這些模組的依賴項(即使您正在構建和測試一個模組,也是如此)。這會減慢大型多模組專案的構建程式

注意:按需配置在新版的Android Studio中已經沒有了

啟用並行化編譯

在嘗試按需配置的過程中發現Compile independent modules in parallel這個選項,查詢一番後發現是使用並行化編譯,能提高編譯速度,勾選即可

在模組化開發中,啟用並行化編譯提高編譯速度更為顯著

建立庫模組

在應用中查詢您可以轉換成 Android 庫模組的程式碼。通過這種方式將您的程式碼模組化可以讓構建系統僅編譯您修改的模組,並快取這些輸出以用於未來構建。這種方式也會讓按需配置和並行專案執行更有效(如果您啟用這些功能)

就是讓你把專案模組化,不再贅述了

為自定義構建邏輯建立任務

在您建立構建分析後,如果分析顯示構建時間中相當大的一部分用在了“配置專案”階段,請檢查 build.gradle 指令碼並查詢您可以新增到自定義 Gradle 任務中的程式碼。將某個構建邏輯移動到任務中後,它僅會在需要時執行,可以為後續構建快取結果,並且該構建邏輯將有資格並行執行(如果您啟用並行專案執行)。要了解詳情,請閱讀官方 Gradle 文件。

就是讓你把一些不是必需執行的構建轉移到Task中執行,比如某個不需要在Debug時執行的構建

配置 dexOptions 和啟用庫預 dexing

啟用這些配置可能加快構建,但是按官網說的,

您應當遞增這些設定的值來試驗它們並通過分析您的構建觀察效果。如果您向程式分配過多的資源,效能可能會下降

所以這個還是得自己判斷是否開啟。至於具體的配置對應的含義,官網已經寫得很清楚了,就直接發出來

  • preDexLibraries宣告是否預 dex 庫依賴項以加快您的增量構建速度。由於此功能可能減慢您的乾淨構建的速度,您可能需要為持續性整合伺服器停用此功能。
  • maxProcessCount設定執行 dex-in-process 時要使用的最大執行緒數量。預設值為 4。
  • javaMaxHeapSize設定 DEX 編譯器的最大堆大小。不過,您應當增加 Gradle 的堆大小(啟用 dex-in-process 時,將與 DEX 編譯器共享),而不是設定此屬性。

增加 Gradle 的堆大小並啟用 dex-in-process

在專案的 gradle.properties 檔案中將 Gradle 的堆大小設定為 2048 MB:

org.gradle.jvmargs = -Xmx2048m
複製程式碼

將影象轉換成 WebP

這也是老生常談了,Android Studio裡就能直接轉換,不過我在專案中並沒有這麼幹

停用 PNG 處理

如果您無法(或者不想)將 PNG 影象轉換成 WebP,仍可以通過在每次構建應用時停用自動影象壓縮的方式加快構建速度。要停用此優化,請將以下程式碼新增到您的 build.gradle 檔案中:

android {
  ...
  aaptOptions {
    cruncherEnabled false
  }
}
複製程式碼

由於構建型別或產品風味不定義此屬性,在構建釋出版本的應用時,您需要將此屬性手動設定為 true

開啟了這個,好像編譯速度又能快一個臺階

啟用 Instant Run

前面提到過了,前提是指令碼中儘量使用靜態依賴。由於目前專案中動態依賴寫得太多,所以這個暫時還是沒有用起來

啟用構建快取

使用 Android 外掛 2.3.0 及更高版本的新專案在預設情況下會啟用構建快取(除非您明確停用構建快取)

停用註解處理器

使用註解處理器(Annotation-Processing-Tool)時,增量 Java 編譯處於停用狀態。如果可以,請嘗試避免使用註解處理器,以便在不同構建之間僅編譯您修改的類

那麼多第三方庫用註解寫的,一般情況下不太可能停用註解處理

但是不使用Butterknife轉而使用findViewById確實能提高編譯速度,因為避免了生成對應的註解類。建議在子元素不多或者頻繁生成時候直接findViewById

尾聲

其實就是一篇官網構建速度優化的讀後感,但按照提示一步步來,構建速度提升確實比較明顯。下一篇會講講如何使用構建分析工具來分析構建速度慢的原因

Optimize your build speed

相關文章