聲明:部落格分享自掘金,寫得太好了,能學習到很多,是以想分享推廣,如果覺得侵權,請聯系我立馬删除
作者:jsonchao
文章标題:深度探索 Gradle 自動化建構技術(一、Gradle 核心配置篇)
部落格位址:https://juejin.im/post/5e924273f265da47f079379c
前言
成為一名優秀的Android開發,需要一份完備的 知識體系,在這裡,讓我們一起成長為自己所想的那樣~。
一、重識 Gradle
工程建構工具從古老的 mk、make、cmake、qmake, 再到成熟的 ant、maven、ivy,最後到如今網際網路時代的 sbt、gradle,經曆了長久的曆史演化與變遷。
Gradle 作為一款新生代的建構工具無疑是有它自身的巨大優勢的,是以,掌握好 Gradle 建構工具的各種使用姿勢與使用場景其重要性不言而喻。
此外,Gradle 已經成為 進階 Android 知識體系 必不可少的一部分。是以,掌握 Gradle,提升自身 自動化建構技術的深度, 能讓我們更加地 如虎添翼。
1、Gradle 是什麼?
- 它是一款強大的建構工具,而不是語⾔。
- 它使用了 Groovy 這個語言,創造了一種 DSL,但它本身不是語⾔。
2、為什麼使用 Gradle?
主要基于如下 三點 原因:
- 它是一個款最新的,功能最強大的建構工具,使用它我們能做很多事情。
- 使用程式替代傳統的 XML 配置,使得項目建構更加靈活。3)、豐富的第三方插件,可以讓我們随心所欲地使用。
- Gradle 的建構流程
通常來說,Gradle 一次完整的建構過程通常分成如下 三個部分:
- 初始化階段:首先,在初始化階段 Gradle 會決定哪些項目子產品要參與建構,并且為每個項目子產品建立一個與之對應的 Project 執行個體。
- 配置階段:然後,配置工程中每個項目的子產品,并執行包含其中的配置腳本。
- 任務執行:最後,執行每個參與建構過程的 Gradle task。
二、打包提速
掌握 Gradle 建構提速的技巧能夠幫助我們節省大量的編譯建構時間,并且,依賴子產品越多且越大的項目節省出來的時間越多,是以是一件投入産出比相當大的事情。
1 更新最新的 Gradle 版本
将 Gradle 和 Android Gradle Plugin 的版本升至最新,所帶來的的建構速度的提升效果是顯而易見的,特别是當之前你所使用的版本很低的時候。
2開啟離線模式
打開 Android Studio 的離線模式後,所有的編譯操作都會走本地緩存,毫無疑問,這将會極大地縮短編譯時間。
3配置 AS 的最大堆記憶體
在預設情況下, AS 的最大堆記憶體為 1960MB,我們可以選擇 Help => Edit Custom VM Options,此時,會打開一個 studio.vmoptions 檔案,我們将第二行的 -Xmx1960m 改為 -Xmx3g 即可将可用記憶體提升到 3GB。
4 删除不必要的 Moudle 或合并部分 Module
過多的 Moudle 會使項目中 Module 的依賴關系變得複雜,Gradle 在編譯建構的時候會去檢測各個 Module 之間的依賴關系,然後,它會花費大量的建構時間幫我們梳理這些 Module 之間的依賴關系,以避免 Module 之間互相引用而帶來的各種問題。除了删除不必要的 Moudle 或合并部分 Module 的方式外,我們也可以将穩定的底層 Module 打包成 aar,上傳到公司的本地 Maven 倉庫,通過遠端方式依賴。
5 删除Module中的無用檔案
- 如果我們不需要寫單元測試代碼,可以直接删除 test 目錄。
- 如果我們不需要寫 UI 測試代碼,也可以直接删除 androidTest 目錄。
- 此外,如果 Moudle 中隻有純代碼,可以直接删除 res 目錄。
6 去除項目中的無用資源
在 Android Studio 中提供了供了自動檢測失效檔案和删除的功能,即 Remove Unused Resource 功能,操作路徑如下所示:
右鍵 => 選中 Refactor => 選中Remove Unused Resource => 直接點選REFACTOR
需要注意的是,這裡不需要将 Delete unused @id declarations too 選中,如果你使用了 databinding 的話,可能會編譯失敗。
7 優化第三方庫的使用
一般的優化步驟有如下 三步:
- 使用更小的庫去替換現有的同類型的三方庫。
-
使用 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' }
- 使用 debugImplementation 來依賴僅在 debug 期間才會使用的庫,如一些線下的性能檢測工具。如下是一個示例代碼:
// 僅在debug包啟用BlockCanary進行卡頓監控和提示的話,可以這麼用 debugImplementation 'com.github.markzhai:blockcanary-android:1.5.0'複制代碼
8 利用公司 Maven 倉庫的本地緩存
當第一個開發引入了新庫或者更新版本之後,公司的 Maven 倉庫中就會緩存對應的庫版本,通過這樣的方式,其他開發同僚就能夠在項目建構時直接從公司的 Maven 倉庫中拿到緩存。
9 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 中采用不同的資源檔案。
10 配置 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
這裡效果比較好一點的配置項就是 配置編譯時的虛拟機大小 這項,我們來詳細分析下其中參數的含義,如下所示:
-
:指定 JVM 最大允許配置設定的堆記憶體為 2048MB,它會采用按需配置設定的方式。-Xmx2048m
-
:指定 JVM 最大允許配置設定的非堆記憶體為 512MB,同上堆記憶體一樣也是按需配置設定的。-XX:MaxPermSize=512m
11 配置 DexOptions
我們可以将 dexOptions 配置項中的 maxProcessCount 設定為 8,這樣編譯時并行的最大程序數數目就可以提升到 8 個。
12 使用 walle 提升打多管道包的效率
walle 是 Android Signature V2 Scheme 簽名下的新一代管道包打包神器,它在 Apk 中的 APK Signature Block 區塊添加了自定義的管道資訊以生成管道包,因而提高了管道包的生成效率。此外,它也可以作為單機工具來使用,也可以部署在 HTTP 伺服器上來實時處理管道包 Apk 的更新網絡請求,有需要的同學可以參考美團的 walle。
13 設定應用支援的語言
如果應用沒有做國際化,我們可以讓應用僅僅支援 中文的資源配置,即将 resConfigs 設定為 “zh”。如下所示:
android {
defaultConfig {
resConfigs "zh"
}
}
14 使用增量編譯
Gradle 的建構方式通常來說細分為以下 三種:
- Full Build:全量建構,即從0開始建構。
- Incremental build java change:增量建構Java改變,修改源代碼後的建構,且之前建構過。
- Incremental build resource change:修改資源檔案後的建構,且之前建構過。
在 Gradle 4.10 版本之後便預設使用了增量編譯,它會測試自上次建構以來是否已更改任何 gradle task 任務輸入或輸出。如果還沒有,Gradle 會将該任務認為是最新的,是以跳過執行其動作。由于 Gradle 可以将項目的依賴關系分析精确到類級别,是以,此時僅會重新編譯受影響的類。如果在更老的版本需要啟動增量編譯,可以使用如下配置:
tasks.withType(JavaCompile) {
options.incremental = true
}
15、使用循環進行依賴優化(🔥)
在 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.gradle 和 config.gradle 上進行檢視。
三、Gradle 常用指令
1、Gradle 查詢指令
1)、檢視主要任務
./gradlew tasks
2)、檢視所有任務,包括緩存任務等等
./gradlew tasks --all
2、Gradle 執行指令
1)、對某個module [moduleName] 的某個任務[TaskName] 運作
./gradlew :moduleName:taskName
3、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
# 簡化版指令,取各個單詞的首字母
./gradlew aD
10)、編譯并打 Release 的包
./gradlew assembleRelease
# 簡化版指令,取各個單詞的首字母
./gradlew aR
4 Gradle 建構并安裝指令
1)、Release 模式打包并安裝
./gradlew installRelease
2)、解除安裝 Release 模式包
./gradlew uninstallRelease
3)、debug release 模式全部管道打包
./gradlew assemble
5、Gradle 檢視包依賴指令
1)、檢視項目根目錄下的依賴
./gradlew dependencies
2)、檢視 app 子產品下的依賴
./gradlew app:dependencies
3)、檢視 app 子產品下包含 implementation 關鍵字的依賴項目
./gradlew app:dependencies --configuration implementation
四、使用 Build Scan 診斷應用的建構過程
在了解 Build Scan 之前,我們需要先來一起學習下舊時代的 Gradle build 診斷工具 Profile report。
1、Profile report
通常情況下,我們一般會使用如下指令來生成一份本地的建構分析報告:
./gradlew assembleDebug --profile
這裡,我們在 Awesome-WanAndroid App的根目錄下運作這個指令,可以得到四塊視圖。下面,我們來了解下。
1)、Summary
Gradle 建構資訊的概覽界面,用于 檢視 Total Build Time、初始化(包含 Startup、Settings and BuildSrc、Loading Projects 三部分)、配置、任務執行的時間。如下圖所示:
2)、Configuaration
Gradle 配置各個工程所花費的時間,我們可以看到 All projects、app 子產品以及其它子產品單個的配置時間。如下圖所示:
3)、Dependency Resolution
Gradle 在對各個 task 進行依賴關系解析時所花費的時間。如下圖所示:
4)、Task Execution
Gradle 在執行各個 Gradle task 所花費的時間。如下圖所示:
需要注意的是,Task Execution 的時間是所有 gradle task 執行時間的總和,實際上 多子產品的任務是并行執行的。
2、Build Scan
Build Scan 是官方推出的用于診斷應用建構過程的性能檢測工具,它能分析出導緻應用建構速度慢的一些問題。在項目下使用如下指令即可開啟 Build Scan 診斷:
./gradlew build --scan
如果你使用的是 Mac,使用上述指令時出現
zsh: permission denied: ./gradlew
可以加入下面的命給 gradlew 配置設定執行權限:
chmod +x gradlew
執行完 build --scan 指令之後,在指令的最後我們可以看到如下資訊:
可以看到,在 Publishing build scan 點選下面的連結就可以跳轉到 Build Scan 的診斷頁面。
需要注意的是,如果你是第一次使用 Build Scan,首先需要使用自己的郵箱激活 Build Scan。如下圖界面所示:
這裡,我輸入了我的郵箱 [email protected],點選 Go!之後,我們就可以登入我們的郵箱去确認授權即可。如下圖所示:
直接點選 Discover your build 即可。
授權成功後,我們就可以看到 Build Scan 的診斷頁面了。如下圖所示:
可以看到,在界面的右邊有一系列的功能 tab 可供我們選擇檢視,這裡預設是 Summary 總覽界面,我們的目的是要檢視 應用的建構性能,是以點選右側的 Performance tab 即可看到如下圖所示的建構分析界面:
從上圖可以看到,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 這一項。如下圖所示:
可以看到,Awesome-WanAndroid 項目中所有的 task 都是 Not cacheable 的。此時,我們往下滑動界面,可以看到所有 task 的建構時間。如下所示:
如果,我們想檢視一個 tinyPicPluginSpeedRelease 這一個 task 的執行詳細,可以點選 :app:tinyPicPluginSpeedRelease 這一項,然後,就會跳轉到 Timeline 界面,顯示出 tinyPicPluginSpeedRelease 相應的執行資訊。如下圖所示:
此外,這裡我們點選彈出框右上方的第一個圖示:Focus on task in timeline 即可看到該 task 在整個 Gradle build 時間線上的精确位置,如下圖所示:
至此,我們可以看到 Build Scan 的功能要比 Profile report 強大不少,是以我強烈建議優先使用它進行 Gradle 建構時間的診斷與優化。
五、總結
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 這樣的外部依賴支援也不好。
但是,Buck 和 Bazel 編譯建構工具内部的優化思路 還是很值得我們學習和參考的,有興趣的同學可以去研究下。下一篇文章,我們将一起來學習 Gradle 中的必備基礎 — groovy,這将會給我們後續的 Gradle 學習打下堅實的基礎,敬請期待。
參考連結:
1、Gradle Github 位址
2、Gradle配置最佳實踐
3、提升 50% 的編譯速度!阿裡零售通 App 工程提效實踐
4、Gradle 提速:每天為你省下一杯喝咖啡的時間
5、[大餐]加快gradle建構速度
6、Gradle子產品化配置:讓你的gradle代碼控制在100行以内
7、Gradle Android-build 常用指令參數及解釋
8、Android打包提速實踐
9、GRADLE建構最佳實踐
作者:jsonchao
連結:https://juejin.im/post/5e924273f265da47f079379c
來源:掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。