深度探索 Gradle 自動化構建技術(1、Gradle 核心配置篇)

前言

成爲一名優秀的Android開發,須要一份完備的 知識體系,在這裏,讓咱們一塊兒成長爲本身所想的那樣~。

1、重識 Gradle

工程構建工具從古老的 mk、make、cmake、qmake, 再到成熟的 ant、maven、ivy,最後到現在互聯網時代的 sbt、gradle,經歷了長久的歷史演化與變遷html

Gradle 做爲一款新生代的構建工具無疑是有它自身的巨大優點的,所以,掌握好 Gradle 構建工具的各類使用姿式與使用場景其重要性不言而喻java

此外,Gradle 已經成爲 高級 Android 知識體系 必不可少的一部分。所以,掌握 Gradle,提高自身 自動化構建技術的深度, 能讓咱們更加地 如虎添翼android

一、Gradle 是什麼?

  • 1)、 它是一款強大的構建工具,而不是語⾔。
  • 2)、 它使用了 Groovy 這個語言,創造了一種 DSL,但它自己不是語⾔。

二、爲何使用 Gradle?

主要基於以下 三點 緣由:git

  • 1)、 它是一個款最新的,功能最強大的構建工具,使用它咱們能作不少事情。
  • 2)、 使用程序替代傳統的 XML 配置,使得項目構建更加靈活。
  • 3)、 豐富的第三方插件,可讓咱們爲所欲爲地使用。

三、Gradle 的構建流程

一般來講,Gradle 一次完整的構建過程一般分紅以下 三個部分github

  • 初始化階段: 首先,在初始化階段 Gradle 會決定哪些項目模塊要參與構建,而且爲每一個項目模塊建立一個與之對應的 Project 實例。
  • 配置階段: 而後,配置工程中每一個項目的模塊,並執行包含其中的配置腳本。
  • 任務執行: 最後,執行每一個參與構建過程的 Gradle task。

2、打包提速

掌握 Gradle 構建提速的技巧可以幫助咱們節省大量的編譯構建時間,而且,依賴模塊越多且越大的項目節省出來的時間越多,所以是一件投入產出比至關大的事情。web

一、升級最新的 Gradle 版本

將 Gradle 和 Android Gradle Plugin 的版本升至最新,所帶來的的構建速度的提高效果是顯而易見的,特別是當以前你所使用的版本很低的時候。json

二、開啓離線模式

打開 Android Studio 的離線模式後,全部的編譯操做都會走本地緩存,毫無疑問,這將會極大地縮短編譯時間。api

三、配置 AS 的最大堆內存

在默認狀況下, AS 的最大堆內存爲 1960MB,咱們能夠選擇 Help => Edit Custom VM Options,此時,會打開一個 studio.vmoptions 文件,咱們將第二行的 -Xmx1960m 改成 -Xmx3g 便可將可用內存提高到 3GB數組

四、刪除沒必要要的 Moudle 或合併部分 Module

過多的 Moudle 會使項目中 Module 的依賴關係變得複雜,Gradle 在編譯構建的時候會去檢測各個 Module 之間的依賴關係,而後,它會花費大量的構建時間幫咱們梳理這些 Module 之間的依賴關係,以免 Module 之間相互引用而帶來的各類問題。除了刪除沒必要要的 Moudle 或合併部分 Module 的方式外,咱們也能夠將穩定的底層 Module 打包成 aar,上傳到公司的本地 Maven 倉庫,經過遠程方式依賴緩存

五、刪除Module中的無用文件

  • 1)、 若是咱們不須要寫單元測試代碼,能夠直接刪除 test 目錄。
  • 2)、 若是咱們不須要寫 UI 測試代碼,也能夠直接刪除 androidTest 目錄。
  • 3)、 此外,若是 Moudle 中只有純代碼,能夠直接刪除 res 目錄。

六、去除項目中的無用資源

在 Android Studio 中提供了供了自動檢測失效文件和刪除的功能,即 Remove Unused Resource 功能,操做路徑以下所示:

右鍵 => 選中 Refactor => 選中Remove Unused Resource => 直接點擊REFACTOR

須要注意的是,這裏不須要將 Delete unused @id declarations too 選中,若是你使用了 databinding 的話,可能會編譯失敗

七、優化第三方庫的使用

通常的優化步驟有以下 三步:

1)、使用更小的庫去替換現有的同類型的三方庫。

2)、使用 exclude 來排除三方庫中某些不須要或者是重複的依賴。

例如,我在 Awesome-WanAndroid 項目中就使用到了這種技巧,在依賴 LeakCanary 時,發現它包含有 support 包,所以,咱們可使用 exclude 將它排除掉,代碼以下所示:

   debugImplementation (rootProject.ext.dependencies["leakcanary-android"]) {
       exclude group: 'com.android.support'
   }
   releaseImplementation (rootProject.ext.dependencies["leakcanary-android-no-op"]) {
       exclude group: 'com.android.support'
   }
   testImplementation (rootProject.ext.dependencies["leakcanary-android-no-op"]) {
       exclude group: 'com.android.support'
   }
複製代碼

3)、使用 debugImplementation 來依賴僅在 debug 期間纔會使用的庫,如一些線下的性能檢測工具。以下是一個示例代碼:

// 僅在debug包啓用BlockCanary進行卡頓監控和提示的話,能夠這麼用
debugImplementation 'com.github.markzhai:blockcanary-android:1.5.0'
複製代碼

八、利用公司 Maven 倉庫的本地緩存

當第一個開發引入了新庫或者更新版本以後,公司的 Maven 倉庫中就會緩存對應的庫版本,經過這樣的方式,其餘開發同事就可以在項目構建時直接從公司的 Maven 倉庫中拿到緩存。

九、Debug 構建時設置 minSdkVersion 爲 21

這樣,咱們就能夠避免因使用 MutliDex 而拖慢 build 速度。在主 Moudle 中的 build.gradle 中加入以下代碼:

    productFlavors {
        speed {
            minSdkVersion 21
        }
    }
複製代碼

同步項目以後,咱們在Android Studio右側的 Build Variants 中選中 speedDebug 選項便可,以下圖所示:

須要注意的是,要注意咱們當前項目的實際最低版本,好比它爲 18,如今咱們開啓了 speedDebug,項目編寫時就會以 21 爲標準,此時,就 須要注意 18 ~ 21 之間的 API,例如我在佈局中使用了 21 版本新出的 Material Design 的控件,此時就是沒問題的,但實際咱們須要對 21 版本如下的對應佈局作相應的適配。

此外,咱們也能夠定義不一樣的 productFlavors,而且在 src 目錄下新建對應的 flavor 名稱標識的目錄資源文件,以此實如今不一樣的渠道 APK 中採用不一樣的資源文件。

十、配置 gradle.properties

通用的配置項以下所示:

    // 構建初始化須要執行許多任務,例如java虛擬機的啓動,加載虛擬機環境,加載class文件等等,配置此項能夠開啓線程守護,而且僅僅第一次編譯時會開啓線程(Gradle 3.0版本之後默認支持)
    org.gradle.daemon=true  
    
    // 配置編譯時的虛擬機大小
    org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8  
    
    // 開啓並行編譯,至關使用了多線程,僅僅適用於模塊化項目(存在多個 Library 庫工程依賴主工程)
    org.gradle.parallel=true  
    
    // 最大的優點在於幫助多 Moudle 的工程提速,在編譯多個 Module 相互依賴的項目時,Gradle 會按需選擇進行編譯,即僅僅編譯相關的 Module
    org.gradle.configureondemand=true   
    
    // 開啓構建緩存,Gradle 3.5新的緩存機制,能夠緩存全部任務的輸出,
    // 不一樣於buildCache僅僅緩存dex的外部libs,它能夠複用
    // 任什麼時候候的構建緩存,設置包括其它分支的構建緩存
    org.gradle.caching=true
複製代碼

這裏效果比較好一點的配置項就是 配置編譯時的虛擬機大小 這項,咱們來詳細分析下其中參數的含義,以下所示:

  • -Xmx2048m: 指定 JVM 最大容許分配的堆內存爲 2048MB,它會採用按需分配的方式。
  • -XX:MaxPermSize=512m: 指定 JVM 最大容許分配的非堆內存爲 512MB,同上堆內存同樣也是按需分配的。

十一、配置 DexOptions

咱們能夠將 dexOptions 配置項中的 maxProcessCount 設定爲 8,這樣編譯時並行的最大進程數數目就能夠提高到 8 個。

十二、使用 walle 提高打多渠道包的效率

walle 是 Android Signature V2 Scheme 簽名下的新一代渠道包打包神器,它在 Apk 中的 APK Signature Block 區塊添加了自定義的渠道信息以生成渠道包,於是提升了渠道包的生成效率。此外,它也能夠做爲單機工具來使用,也能夠部署在 HTTP 服務器上來實時處理渠道包 Apk 的升級網絡請求,有須要的同窗能夠參考美團的 walle

1三、設置應用支持的語言

若是應用沒有作國際化,咱們可讓應用僅僅支持 中文的資源配置,即將 resConfigs 設置爲 "zh"。以下所示:

    android {
        defaultConfig {
            resConfigs "zh"
        }
    }
複製代碼

1四、使用增量編譯

Gradle 的構建方式一般來講細分爲如下 三種:

  • 1)、 Full Build: 全量構建,即從0開始構建。
  • 2)、 Incremental build java change: 增量構建Java改變,修改源代碼後的構建,且以前構建過。
  • 3)、 Incremental build resource change: 修改資源文件後的構建,且以前構建過。

在 Gradle 4.10 版本以後便默認使用了增量編譯,它會測試自上次構建以來是否已更改任何 gradle task 任務輸入或輸出。若是尚未,Gradle 會將該任務認爲是最新的,所以跳過執行其動做。因爲 Gradle 能夠將項目的依賴關係分析精確到類級別,所以,此時僅會從新編譯受影響的類。若是在更老的版本須要啓動增量編譯,可使用以下配置:

    tasks.withType(JavaCompile) {
        options.incremental = true
    }
複製代碼

1五、使用循環進行依賴優化(🔥)

在 Awesome-WanAndroid 項目的 app moudle 的 build.gradle 中,有將近幾百行的依賴代碼,以下所示:

    dependencies {
        implementation fileTree(include: ['*.jar'], dir: 'libs')

        // 啓動器
        api files('libs/launchstarter-release-1.0.0.aar')
        
         //base
        implementation rootProject.ext.dependencies["appcompat-v7"]
        implementation rootProject.ext.dependencies["cardview-v7"]
        implementation rootProject.ext.dependencies["design"]
        implementation rootProject.ext.dependencies["constraint-layout"]
        
        annotationProcessor rootProject.ext.dependencies["glide_compiler"]
        
         //canary
        debugImplementation (rootProject.ext.dependencies["leakcanary-android"]) {
            exclude group: 'com.android.support'
        }
        releaseImplementation (rootProject.ext.dependencies["leakcanary-android-no-op"]) {
            exclude group: 'com.android.support'
        }
        testImplementation (rootProject.ext.dependencies["leakcanary-android-no-op"]) {
            exclude group: 'com.android.support'
        }
        
        ...
複製代碼

有沒有一種好的方式不在 build.gradle 中寫這麼多的依賴配置?

有,就是 使用循環遍歷依賴。答案彷佛很簡單,可是要想處理在依賴時遇到的全部狀況,並不簡單。下面,我直接給出相應的適配代碼,你們能夠直接使用。

首先,在 app 下的 build.gradle 的依賴配置以下所示:

    // 處理全部的 aar 依賴
    apiFileDependencies.each { k, v -> api files(v)}

    // 處理全部的 xxximplementation 依賴
    implementationDependencies.each { k, v -> implementation v }
    debugImplementationDependencies.each { k, v -> debugImplementation v }
    releaseImplementationDependencies.each { k, v -> releaseImplementation v }
    androidTestImplementationDependencies.each { k, v -> androidTestImplementation v }
    testImplementationDependencies.each { k, v -> testImplementation v }
    debugApiDependencies.each { k, v -> debugApi v }
    releaseApiDependencies.each { k, v -> releaseApi v }
    compileOnlyDependencies.each { k, v -> compileOnly v }
    
    // 處理 annotationProcessor 依賴
    processors.each { k, v -> annotationProcessor v }
    
    // 處理全部包含 exclude 的依賴
    implementationExcludes.each { entry ->
        implementation(entry.key) {
            entry.value.each { childEntry ->
                exclude(group: childEntry)
            }
        }
    }
    debugImplementationExcludes.each { entry ->
        debugImplementation(entry.key) {
            entry.value.each { childEntry ->
                exclude(group: childEntry.key, module: childEntry.value)
            }
        }
    }
    releaseImplementationExcludes.each { entry ->
        releaseImplementation(entry.key) {
            entry.value.each { childEntry ->
                exclude(group: childEntry.key, module: childEntry.value)
            }
        }
    }
    testImplementationExclude.each { entry ->
        testImplementation(entry.key) {
            entry.value.each { childEntry ->
                exclude(group: childEntry.key, module: childEntry.value)
            }
        }
    }
    androidTestImplementationExcludes.each { entry ->
        androidTestImplementation(entry.key) {
            entry.value.each { childEntry ->
                exclude(group: childEntry.key, module: childEntry.value)
            }
        }
    }
複製代碼

而後,在 config.gradle 全局依賴管理文件中配置好對應名稱的依賴數組便可。代碼以下所示:

    dependencies = [
            // base
            "appcompat-v7"                      : "com.android.support:appcompat-v7:${version["supportLibraryVersion"]}"
            ...
    ]
    
    annotationProcessor = [
            "glide_compiler"                    : "com.github.bumptech.glide:compiler:${version["glideVersion"]}",
            ...
    ]
    
    apiFileDependencies = [
            "launchstarter"                                   :"libs/launchstarter-release-1.0.0.aar"
    ]
    
    debugImplementationDependencies = [
            "MethodTraceMan"                                  : "com.github.zhengcx:MethodTraceMan:1.0.7"
    ]
    
    ...
    
    implementationExcludes = [
            "com.android.support.test.espresso:espresso-idling-resource:3.0.2" : [
                    'com.android.support' : 'support-annotations'
            ]
    ]
    
    ...
複製代碼

具體的代碼示例能夠在 Awesome-WanAndroid 的 build.gradleconfig.gradle 上進行查看。

3、Gradle 經常使用命令

一、Gradle 查詢命令

1)、查看主要任務

    ./gradlew tasks
複製代碼

2)、查看全部任務,包括緩存任務等等

    ./gradlew tasks --all
複製代碼

二、Gradle 執行命令

1)、對某個module [moduleName] 的某個任務[TaskName] 運行

    ./gradlew :moduleName:taskName
複製代碼

三、Gradle 快速構建命令

Gradle 提供了一系列的快速構建命令來替代 IDE 的可視化構建操做,如咱們最經常使用的 clean、build 等等。須要注意的是,build 命令會把 debug、release 環境的包都構建出來。

1)、查看構建版本

    ./gradlew -v
複製代碼

2)、清除 build 文件夾

    ./gradlew clean
複製代碼

3)、檢查依賴並編譯打包

    ./gradlew build
複製代碼

4)、編譯並安裝 debug 包

    ./gradlew installDebug
複製代碼

5)、編譯並打印日誌

    ./gradlew build --info
複製代碼

6)、編譯並輸出性能報告,性能報告通常在構建工程根目錄 build/reports/profile 下

    ./gradlew build --profile
複製代碼

7)、調試模式構建並打印堆棧日誌

    ./gradlew build --info --debug --stacktrace
複製代碼

8)、強制更新最新依賴,清除構建後再構建

    ./gradlew clean build --refresh-dependencies
複製代碼

9)、編譯並打 Debug 包

    ./gradlew assembleDebug
    # 簡化版命令,取各個單詞的首字母
    ./g
radlew aD
複製代碼

10)、編譯並打 Release 的包

    ./gradlew assembleRelease
    # 簡化版命令,取各個單詞的首字母
    ./g
radlew aR
複製代碼

四、Gradle 構建並安裝命令

1)、Release 模式打包並安裝

    ./gradlew installRelease
複製代碼

2)、卸載 Release 模式包

    ./gradlew uninstallRelease
複製代碼

3)、debug release 模式所有渠道打包

    ./gradlew assemble
複製代碼

五、Gradle 查看包依賴命令

1)、查看項目根目錄下的依賴

    ./gradlew dependencies
複製代碼

2)、查看 app 模塊下的依賴

    ./gradlew app:dependencies
複製代碼

3)、查看 app 模塊下包含 implementation 關鍵字的依賴項目

    ./gradlew app:dependencies --configuration implementation
複製代碼

4、使用 Build Scan 診斷應用的構建過程

在瞭解 Build Scan 以前,咱們須要先來一塊兒學習下舊時代的 Gradle build 診斷工具 Profile report。

一、Profile report

一般狀況下,咱們通常會使用以下命令來生成一份本地的構建分析報告:

    ./gradlew assembleDebug --profile
複製代碼

這裏,咱們在 Awesome-WanAndroid App的根目錄下運行這個命令,能夠獲得四塊視圖。下面,咱們來了解下。

1)、Summary

Gradle 構建信息的概覽界面,用於 查看 Total Build Time、初始化(包含 Startup、Settings and BuildSrc、Loading Projects 三部分)、配置、任務執行的時間。以下圖所示:

image
image

2)、Configuaration

Gradle 配置各個工程所花費的時間,咱們能夠看到 All projects、app 模塊以及其它模塊單個的配置時間。以下圖所示:

image
image

3)、Dependency Resolution

Gradle 在對各個 task 進行依賴關係解析時所花費的時間。以下圖所示:

image
image

4)、Task Execution

Gradle 在執行各個 Gradle task 所花費的時間。以下圖所示:

image
image

須要注意的是,Task Execution 的時間是全部 gradle task 執行時間的總和,實際上 多模塊的任務是並行執行的

二、Build Scan

Build Scan 是官方推出的用於診斷應用構建過程的性能檢測工具,它能分析出致使應用構建速度慢的一些問題。在項目下使用以下命令便可開啓 Build Scan 診斷:

    ./gradlew build --scan 
複製代碼

若是你使用的是 Mac,使用上述命令時出現

    zsh: permission denied: ./gradlew
複製代碼

能夠加入下面的命給 gradlew 分配執行權限:

    chmod +x gradlew
複製代碼

執行完 build --scan 命令以後,在命令的最後咱們能夠看到以下信息:

image
image

能夠看到,在 Publishing build scan 點擊下面的連接就能夠跳轉到 Build Scan 的診斷頁面。

須要注意的是,若是你是第一次使用 Build Scan,首先須要使用本身的郵箱激活 Build Scan。以下圖界面所示:

image
image

這裏,我輸入了個人郵箱 chao.qu521@gmail.com,點擊 Go!以後,咱們就能夠登陸咱們的郵箱去確認受權便可。以下圖所示:

image
image

直接點擊 Discover your build 便可。

受權成功後,咱們就能夠看到 Build Scan 的診斷頁面了。以下圖所示:

image
image

能夠看到,在界面的右邊有一系列的功能 tab 可供咱們選擇查看,這裏默認是 Summary 總覽界面,咱們的目的是要查看 應用的構建性能,因此點擊右側的 Performance tab 便可看到以下圖所示的構建分析界面:

image
image

從上圖能夠看到,Performance 界面中除了 Build、Configuration、Dependency resolution、Task execution 這四項外,還有 Daemon、Network activity、Settings and suggestions

在 Build 界面中,共有三個子項目,即 Total build time、Total garbage collection time、Peak heap memory usage,Total build time 裏面的配置項前面咱們已經分析過了,這裏咱們看看其他兩項的含義,以下所示:

  • Total garbage collection time: 總的垃圾回收時間。
  • Peak heap memory usage: 最大堆內存使用。

對於 Peak heap memory usage 這一項來講,還有三個子項,其含義以下:

  • 1)、 PS Eden Space: Young Generation 的 Eden(伊甸園)物理內存區域。程序中生成的大部分新的對象都在 Eden 區中。
  • 2)、 PS Survivor Space: Young Generation 的 Eden 的 兩個Survivor(倖存者)物理內存區域。當 Eden 區滿時,還存活的對象將被複制到其中一個 Survivor 區,當此 Survivor 區滿時,此區存活的對象又被複制到另外一個 Survivor 區,當這個 Survivor 區也滿時,會將其中存活的對象複製到年老代。
  • 3)、 PS Old Gen: Old Generation,通常狀況下,年老代中的對象生命週期都比較長。

因爲咱們的目的是關注項目的 build 時間,因此,咱們直接關注到 Task execution 這一項。以下圖所示:

image
image

能夠看到,Awesome-WanAndroid 項目中全部的 task 都是 Not cacheable 的。此時,咱們往下滑動界面,能夠看到全部 task 的構建時間。以下所示:

image
image

若是,咱們想查看一個 tinyPicPluginSpeedRelease 這一個 task 的執行詳細,能夠點擊 :app:tinyPicPluginSpeedRelease 這一項,而後,就會跳轉到 Timeline 界面,顯示出 tinyPicPluginSpeedRelease 相應的執行信息。以下圖所示:

image
image

此外,這裏咱們點擊彈出框右上方的第一個圖標:Focus on task in timeline 便可看到該 task 在整個 Gradle build 時間線上的精確位置,以下圖所示:

image
image

至此,咱們能夠看到 Build Scan 的功能要比 Profile report 強大很多,因此我強烈建議優先使用它進行 Gradle 構建時間的診斷與優化。

5、總結

Gradle 每次構建的運行時間會隨着項目編譯次數越來少,所以爲了準確評估 Gradle 構建提速的優化效果,咱們能夠在優化先後分別執行如下命令進行對比分析,以下所示:

    gradlew --profile --recompile-scripts --offline --rerun-tasks assembleDebug
複製代碼

參數含義以下:

  • profile: 開啓性能檢測。
  • recompile-scripts: 不使用緩存,直接從新編譯腳本。
  • offline: 啓用離線編譯模式。
  • return-task: 運行全部 gradle task 並忽略全部優化。

此外,Facebook 的 Buck 以及 Google 的 Bazel 都是優秀的編譯工具,那麼他們爲何沒有使用開源的構建工具呢,主要有以下 三點緣由:

  • 1)、 統一編譯工具: 內部的全部項目都使用同一套構建工具,包括 Android、Java、iOS、Go、C++ 等。編譯工具的統一優化會使全部項目受益。
  • 2)、 代碼組織管理架構: Facebook 和 Google 的全部項目都放到同一個倉庫裏面,所以整個倉庫很是龐大,而且,他們也不會使用 Git。目前 Google 使用的是Piper,Facebook 是基於HG修改的,也是一種基於分佈式的文件系統。
  • 3)、 極致的性能追求: Buck 和 Bazel 的性能的確比 Gradle 更好,內部包含它們的各類編譯優化。可是它們的定製型太強,並且對 Maven、JCenter 這樣的外部依賴支持也很差。

可是,BuckBazel 編譯構建工具內部的優化思路 仍是很值得咱們學習和參考的,有興趣的同窗能夠去研究下。下一篇文章,咱們將一塊兒來學習 Gradle 中的必備基礎 — groovy,這將會給咱們後續的 Gradle 學習打下堅實的基礎,敬請期待。

參考連接:


一、Gradle Github 地址

二、Gradle配置最佳實踐

三、提高 50% 的編譯速度!阿里零售通 App 工程提效實踐

四、Gradle 提速:天天爲你省下一杯喝咖啡的時間

五、[大餐]加快gradle構建速度

六、Gradle模塊化配置:讓你的gradle代碼控制在100行之內

七、Gradle Android-build 經常使用命令參數及解釋

八、Android打包提速實踐

九、GRADLE構建最佳實踐

Contanct Me

● 微信:

歡迎關注個人微信:bcce5360

● 微信羣:

因爲微信羣已超過 200 人,麻煩你們想進微信羣的朋友們,加我微信拉你進羣。

● QQ羣:

2千人QQ羣,Awesome-Android學習交流羣,QQ羣號:959936182, 歡迎你們加入~

About me

很感謝您閱讀這篇文章,但願您能將它分享給您的朋友或技術羣,這對我意義重大。

但願咱們能成爲朋友,在 Github掘金上一塊兒分享知識。

相關文章
相關標籤/搜索