工程構建工具從古老的 mk、make、cmake、qmake, 再到成熟的 ant、maven、ivy,最後到現在互聯網時代的 sbt、gradle,經歷了長久的歷史演化與變遷。html
Gradle 做爲一款新生代的構建工具無疑是有它自身的巨大優點的,所以,掌握好 Gradle 構建工具的各類使用姿式與使用場景其重要性不言而喻。java
此外,Gradle 已經成爲 高級 Android 知識體系 必不可少的一部分。所以,掌握 Gradle,提高自身 自動化構建技術的深度, 能讓咱們更加地 如虎添翼。android
主要基於以下 三點 緣由:git
一般來講,Gradle 一次完整的構建過程一般分紅以下 三個部分:github
掌握 Gradle 構建提速的技巧可以幫助咱們節省大量的編譯構建時間,而且,依賴模塊越多且越大的項目節省出來的時間越多,所以是一件投入產出比至關大的事情。web
將 Gradle 和 Android Gradle Plugin 的版本升至最新,所帶來的的構建速度的提高效果是顯而易見的,特別是當以前你所使用的版本很低的時候。json
打開 Android Studio 的離線模式後,全部的編譯操做都會走本地緩存,毫無疑問,這將會極大地縮短編譯時間。api
在默認狀況下, AS 的最大堆內存爲 1960MB,咱們能夠選擇 Help => Edit Custom VM Options,此時,會打開一個 studio.vmoptions 文件,咱們將第二行的 -Xmx1960m 改成 -Xmx3g 便可將可用內存提高到 3GB。數組
過多的 Moudle 會使項目中 Module 的依賴關係變得複雜,Gradle 在編譯構建的時候會去檢測各個 Module 之間的依賴關係,而後,它會花費大量的構建時間幫咱們梳理這些 Module 之間的依賴關係,以免 Module 之間相互引用而帶來的各類問題。除了刪除沒必要要的 Moudle 或合併部分 Module 的方式外,咱們也能夠將穩定的底層 Module 打包成 aar,上傳到公司的本地 Maven 倉庫,經過遠程方式依賴。緩存
在 Android Studio 中提供了供了自動檢測失效文件和刪除的功能,即 Remove Unused Resource 功能,操做路徑以下所示:
右鍵 => 選中 Refactor => 選中Remove Unused Resource => 直接點擊REFACTOR
須要注意的是,這裏不須要將 Delete unused @id declarations too 選中,若是你使用了 databinding 的話,可能會編譯失敗。
通常的優化步驟有以下 三步:
例如,我在 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'
}
複製代碼
// 僅在debug包啓用BlockCanary進行卡頓監控和提示的話,能夠這麼用
debugImplementation 'com.github.markzhai:blockcanary-android:1.5.0'
複製代碼
當第一個開發引入了新庫或者更新版本以後,公司的 Maven 倉庫中就會緩存對應的庫版本,經過這樣的方式,其餘開發同事就可以在項目構建時直接從公司的 Maven 倉庫中拿到緩存。
這樣,咱們就能夠避免因使用 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 中採用不一樣的資源文件。
通用的配置項以下所示:
// 構建初始化須要執行許多任務,例如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
複製代碼
這裏效果比較好一點的配置項就是 配置編譯時的虛擬機大小 這項,咱們來詳細分析下其中參數的含義,以下所示:
咱們能夠將 dexOptions 配置項中的 maxProcessCount 設定爲 8,這樣編譯時並行的最大進程數數目就能夠提高到 8 個。
walle 是 Android Signature V2 Scheme 簽名下的新一代渠道包打包神器,它在 Apk 中的 APK Signature Block 區塊添加了自定義的渠道信息以生成渠道包,於是提升了渠道包的生成效率。此外,它也能夠做爲單機工具來使用,也能夠部署在 HTTP 服務器上來實時處理渠道包 Apk 的升級網絡請求,有須要的同窗能夠參考美團的 walle。
若是應用沒有作國際化,咱們可讓應用僅僅支持 中文的資源配置,即將 resConfigs 設置爲 "zh"。以下所示:
android {
defaultConfig {
resConfigs "zh"
}
}
複製代碼
Gradle 的構建方式一般來講細分爲如下 三種:
在 Gradle 4.10 版本以後便默認使用了增量編譯,它會測試自上次構建以來是否已更改任何 gradle task 任務輸入或輸出。若是尚未,Gradle 會將該任務認爲是最新的,所以跳過執行其動做。因爲 Gradle 能夠將項目的依賴關係分析精確到類級別,所以,此時僅會從新編譯受影響的類。若是在更老的版本須要啓動增量編譯,可使用以下配置:
tasks.withType(JavaCompile) {
options.incremental = true
}
複製代碼
在 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 上進行查看。
./gradlew tasks
複製代碼
./gradlew tasks --all
複製代碼
./gradlew :moduleName:taskName
複製代碼
Gradle 提供了一系列的快速構建命令來替代 IDE 的可視化構建操做,如咱們最經常使用的 clean、build 等等。須要注意的是,build 命令會把 debug、release 環境的包都構建出來。
./gradlew -v
複製代碼
./gradlew clean
複製代碼
./gradlew build
複製代碼
./gradlew installDebug
複製代碼
./gradlew build --info
複製代碼
./gradlew build --profile
複製代碼
./gradlew build --info --debug --stacktrace
複製代碼
./gradlew clean build --refresh-dependencies
複製代碼
./gradlew assembleDebug
# 簡化版命令,取各個單詞的首字母
./gradlew aD
複製代碼
./gradlew assembleRelease
# 簡化版命令,取各個單詞的首字母
./gradlew aR
複製代碼
./gradlew installRelease
複製代碼
./gradlew uninstallRelease
複製代碼
./gradlew assemble
複製代碼
./gradlew dependencies
複製代碼
./gradlew app:dependencies
複製代碼
./gradlew app:dependencies --configuration implementation
複製代碼
在瞭解 Build Scan 以前,咱們須要先來一塊兒學習下舊時代的 Gradle build 診斷工具 Profile report。
一般狀況下,咱們通常會使用以下命令來生成一份本地的構建分析報告:
./gradlew assembleDebug --profile
複製代碼
這裏,咱們在 Awesome-WanAndroid App的根目錄下運行這個命令,能夠獲得四塊視圖。下面,咱們來了解下。
Gradle 構建信息的概覽界面,用於 查看 Total Build Time、初始化(包含 Startup、Settings and BuildSrc、Loading Projects 三部分)、配置、任務執行的時間。以下圖所示:
Gradle 配置各個工程所花費的時間,咱們能夠看到 All projects、app 模塊以及其它模塊單個的配置時間。以下圖所示:
Gradle 在對各個 task 進行依賴關係解析時所花費的時間。以下圖所示:
Gradle 在執行各個 Gradle task 所花費的時間。以下圖所示:
須要注意的是,Task Execution 的時間是全部 gradle task 執行時間的總和,實際上 多模塊的任務是並行執行的。
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。以下圖界面所示:
這裏,我輸入了個人郵箱 chao.qu521@gmail.com,點擊 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 裏面的配置項前面咱們已經分析過了,這裏咱們看看其他兩項的含義,以下所示:
對於 Peak heap memory usage 這一項來講,還有三個子項,其含義以下:
因爲咱們的目的是關注項目的 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
複製代碼
參數含義以下:
此外,Facebook 的 Buck 以及 Google 的 Bazel 都是優秀的編譯工具,那麼他們爲何沒有使用開源的構建工具呢,主要有以下 三點緣由:
可是,Buck 和 Bazel 編譯構建工具內部的優化思路 仍是很值得咱們學習和參考的,有興趣的同窗能夠去研究下。下一篇文章,咱們將一塊兒來學習 Gradle 中的必備基礎 — groovy,這將會給咱們後續的 Gradle 學習打下堅實的基礎,敬請期待。
三、提高 50% 的編譯速度!阿里零售通 App 工程提效實踐
六、Gradle模塊化配置:讓你的gradle代碼控制在100行之內
七、Gradle Android-build 經常使用命令參數及解釋
歡迎關注個人微信:
bcce5360
因爲微信羣已超過 200 人,麻煩你們想進微信羣的朋友們,加我微信拉你進羣。
2千人QQ羣,Awesome-Android學習交流羣,QQ羣號:959936182, 歡迎你們加入~