# 優化建置速度 <center> <img src="https://i.imgur.com/CEyTy2e.jpg" alt="Cowman" style="border-radius:5px; box-shadow:5px 5px 10px rgba(0, 0, 0, 0.4)" border="10"/> </center> <center> <font color"gray"> Photo by <a href="https://unsplash.com/@alternateskate?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Alternate Skate</a> on <a href="hhttps://medium.com/r/?url=https%3A%2F%2Funsplash.com%2Fs%2Fphotos%2Fspeed%3Futm_source%3Dunsplash%26utm_medium%3Dreferral%26utm_content%3DcreditCopyText">Unsplash</a> </font> </center> ## Gradlew 在動工前先用gradlew指令看看實驗前,實驗後的結果看看吧!那**gradlew**是什麼呢?在講gradlew前先了解下Gradle這個可能很親近卻陌生的工具。 Gradle是一個構建工具,它用來幫助我們構建app,構建包括編譯打包等過程。我們可以為Gradle指定構建規則,那我們想用gradle的指令構建app,就需要用到gradlew(即gradle wrapper的簡寫)。 那如果想詳細了解gradlew的相關指令可以參考下方這篇文章,寫得非常詳盡。 [Android工具學習之gradlew最全指令攻略](https://www.twblogs.net/a/5d4281b0bd9eee51fbf9de1c) 1. 清除gradlew,開啟android studio選擇專案後,點選下方Terminal並輸入下方gradlew指令,清空gradlew。 ```= ./gradlew clean ``` 2. 接著依照你專案的名稱下去更換,中間的命令也可以參考gradlew進行更換,但最主要的是**Flavor**將它更換成你的build專案名稱。這邊就先照Google devlopers來做吧!可以參考這篇[Profile your build](https://developer.android.com/studio/build/optimize-your-build#profile)。 ```= ./gradlew --profile --offline --rerun-tasks assembleFlavorDebug ``` 3. 結束後會產出個html的報告,放在你專案資料夾底下,進入專案資料夾後路徑如下。 `build => reports => profile => profile-2020-01-03-17-09-34.html` 用預覽器開啟這個html檔就會看到如下圖的報告了。 ![](https://i.imgur.com/LAHf6KU.png) 到目前為止你已經會使用gradlew指令產出profile report了,接著就來開始動手優化建置速度吧! ## 優化建置速度 ### 1. update gradle version 最新的Gradle version可以參考右側的連結[Android Gradle plugin release notes](https://developer.android.com/studio/releases/gradle-plugin) `+代表添加`;`-代表刪除` ```kotlin=1 buildscript { repositories { jcenter() + maven { url 'https://maven.google.com'} } dependencies { - classpath 'com.android.tools.build:gradle:2.2.0' + classpath 'com.android.tools.build:gradle:3.3.0-alpha1' } } ``` 升級你的Gradle版本,從圖表中可以看到2.2.0升到3.0.0-alpha1的差距了。 ![use latest android gradle](https://i.imgur.com/m4WJn5F.png) ### 2. Avoid legacy multidex 在Android Sutdio 2.3版本後已經預設幫你處理這部分的事情了,因此不必在defaultConfig設定後又在productFlavors啟動一次multidex,所以放心的拿掉productFlavors裡的~~minSdkVersion 21~~吧! ```kotlin=1 productFlavors { dev { - minSdkVersion 21 ... } } ``` 那啟動它會花多久?這會讓專案建置又拖了幾秒鐘?讓我們來看看Google I/O '17的圖表吧! ![avoid multidex](https://i.imgur.com/BjOrHLz.png) ### 3. Disable multi apk 這裡就要看你的build type了,在Google I/O '17中他們是針對gradle.properties file中是否有devBuild下去設定,但我只想要在build是dev時才做disable,因此就Google到了另一種方式,兩種都會附上去給大家看看你需要哪一種。 <center> <a href="https://youtu.be/7ll-rkLCtyk?t=531">Google I/O '17</a> </center> ```kotlin=1 android { if(project.hasProperty('devBuild')) { splits.abi.enable = false splits.desity.enable = false } } ``` <center> <a href="https://stackoverflow.com/questions/21221868/exclude-specific-build-variants">Exclude specific build variants</a> </center> ```kotlin=1 variantFilter { variant -> def names = variant.flavors*.name // To check for a certain build type, use variant.buildType.name == "<buildType>" if (names.contains("dev")) { // Prevent multi apk generation on development splits.abi.enable = false splits.density.enable = false ... } } ``` 因為Google I/O '17沒有放實測數據,那我們就來測試下來會差多少呢? 在這邊我們實測下來大概快了35秒,下面來看下報告吧~ --- **splits abi & density open** ![splits abi & density open](https://i.imgur.com/1pKx37Z.png) --- **splits abi & density close** ![splits abi & density close](https://i.imgur.com/tgwJIcL.png) ### 4. include minimal resources 賦予debug專案指定的資源檔,這也有助於加快建置速度,但如果你都使用webp的圖檔的話那這段其實幫助沒有到這麼顯著,稍後會解釋webp。 ```kotlin=1 productFlavors { development { minSdkVersion 21 resConfigs ("en","xxhdpi") ... } } ``` ### 5. Disable PNG crunching 禁用PNG,原因是因為PNG在進行建置時會再進行處理,如果改用Webp後就不用PNG了,如果一定要用PNG可以略過此方法,用上方指定資源檔即可。那這邊我同樣附給大家Google I/O及我的版本,差異如下。 <center> <a href="https://youtu.be/7ll-rkLCtyk?t=531">Google I/O '17</a> </center> ```kotlin=1 android { if(project.hasProperty('devBuild')) { splits.abi.enable = false splits.density.enable = false aaptOptions.cruncherEnabled = false } } ``` <center> <a href="https://stackoverflow.com/questions/21221868/exclude-specific-build-variants">Exclude specific build variants</a> </center> ```kotlin=1 variantFilter { variant -> def names = variant.flavors*.name if (names.contains("dev")) { splits.abi.enable = false splits.density.enable = false aaptOptions.cruncherEnabled = false } } ``` 那一樣看看Google I/O '17的圖表,了解下差異有多少。 ![Disable png crunching](https://i.imgur.com/AzrdSio.png) ### 6. Use WebP 那怎麼創建WebP呢?這裡很貼心的可以使用Android Studio直接轉換的方法就參照官方開發者文件說明拉!請參考此篇[Create WebP images](https://developer.android.com/studio/write/convert-webp) 替換完後會差到多少呢?這邊實測下來大約差到30秒,做到這裡拼拼湊湊起碼也一分鐘了。 <center> <font color="green"> Use PNG & JPG </font> </center> ![PNG Image](https://i.imgur.com/4AwaNfm.png) <center> <font color="green"> Use Webp </font> </center> ![WebP Image](https://i.imgur.com/wMjJ44c.png) ### 7. Disable Crashlytics and Update Frabic BuildId 相信各位Android工程師一定有使用過Firebase的產品,在這裡可以在特定build type中關閉,因為Crashlytics及Fabic BuildId都會拖慢建置速度。 我這邊是想在build type是dev時且又是debug下載禁用crashlytics,所以才會演變下方的方式。 <center> <font color="green"> Application </font> </center> ```kotlin=1 val crashlytics = if (BuildConfig.FLAVOR != "dev") { Crashlytics() } else { Crashlytics.Builder() .core(CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build()) .build() } Fabric.with(this, crashlytics) ``` <center> <font color="green"> build.gradle </font> </center> ```kotlin=1 variantFilter { variant -> def names = variant.flavors*.name if (names.contains("dev")) { splits.abi.enable = false splits.density.enable = false aaptOptions.cruncherEnabled = false // Disable crashlytics ext.enableCrashlytics = false ext.alwaysUpdateBuildId = false } } ``` 那這樣快了幾秒呢?測下來大概快了5秒鐘 <center> <font color="green"> Open Crashlytics and Update Frabic BuildId </font> </center> ![Open Crashlytics and Update Frabic BuildId](https://i.imgur.com/VBa98bx.png) <center> <font color="green"> Close Crashlytics and Update Frabic BuildId </font> </center> ![Close Crashlytics and Update Frabic BuildId](https://i.imgur.com/wZdE2z4.png) ### 8. Don't use dynamic versions 因為使用dynamic version會讓專案每次建置時都需要上去拉一次最新的資源,而且也有可能會因為新版與舊版不相容,間接增加穩定性降低的風險,所以應該如下實作。 Bad : ~~`implementation 'com.lokalise.android:ota-sdk:1.+'`~~ Good : `implementation 'com.lokalise.android:ota-sdk:1.3.15'` ### 9. 民間偏方 接下來官方大補帖都試完了,就輪到民間偏方了以下是我嘗試的民間偏方,kapt我沒有完整實作,因為試下去好像沒有顯著效果不然原本想對room使用此方式。 <center> <font color="green"> build.gradle </font> </center> ```kotlin= android { buildTypes { ... debug { minifyEnabled false // Disable PNG crunching. "It's available since Android Studio 3.0 Canary 5 release." crunchPngs false } } dexOptions { javaMaxHeapSize '2g' //Google I/O '17 say be careful use jumboMode = true preDexLibraries = true threadCount = 8 maxProcessCount 8 } kapt { useBuildCache = true correctErrorTypes = true javacOptions { // 增加注解處理器的最大錯誤次數 // 默認為 100。 option("-Xmaxerrs", 500) } } } ``` <center> <font color="green"> gradle.properties </font> </center> ```kotlin=1 #Specifies the scheduling priority for the Gradle daemon and all processes launched by it. Default is normal. See also performance command-line options. org.gradle.daemon=true #When configured, Gradle will fork up to org.gradle.workers.max JVMs to execute projects in parallel. To learn more about parallel task execution, see the Gradle performance guide. org.gradle.parallel=true #Enables incubating configuration on demand, where Gradle will attempt to configure only necessary projects. org.gradle.configureondemand=true #調整jvm大小 org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=4096m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects #開啟Gradle暫存 org.gradle.caching=true android.enableBuildCache=true android.databinding.enableV2=true android.debug.obsoleteApi=true android.databinding.incremental=true android.lifecycleProcessor.incremental=true #kotlin kotlin.incremental=true kotlin.incremental.java=true kotlin.incremental.js=true kotlin.caching.enabled=true kotlin.parallel.tasks.in.project=true #support kapt1.2.60+ kapt.use.worker.api=true #support kapt1.3.30+ kapt.incremental.apt=true #如果kapt內容沒有變化,重用編譯內容,節省`app:kaptGenerateStubsDebugKotlin`時間 kapt.include.compile.classpath=false # 說明文件 https://docs.gradle.org/current/userguide/build_environment.html ``` ## 總結 優化就跟賽車一樣,為的就是榨出這輛車的每匹馬力,把性能逼至極限,純粹為了那幾毫秒的勝利,最後給大家兩句Gradlew指令可以了解下自己調教出來的建置速度有沒有達到自己心中的目標呢? <center> <font color="green"> 產出原始未優化報告 </font> </center> ``` ./gradlew --profile --offline --rerun-tasks assembleFlavorDebug ``` <center> <font color="green"> 產出優化後報告 </font> </center> ``` ./gradlew --profile --offline assembleFlavorDebug ``` 最後附上Before & After感謝讀完! <center> <font color="green"> Before </font> </center> ![](https://i.imgur.com/YbdcqdM.png) <center> <font color="green"> After </font> </center> ![](https://i.imgur.com/rOrMPGW.png) --- ### 參考 - [Configure product flavors](https://developer.android.com/studio/build/build-variants) - [Optimize your build speed](https://developer.android.com/studio/build/optimize-your-build) - [Speed up Android Gradle builds](https://medium.com/@hung_yanbin/speed-up-android-gradle-builds-c6976d38f83a) - [Speed Up Your Android Project's Gradle Builds!](https://dev.to/joshuadeguzman/speed-up-your-android-project-s-gradle-builds-1366) - [Speeding Up Your Android Gradle Builds (Google I/O '17)](https://youtu.be/7ll-rkLCtyk) ###### tags: `Optimize Build` `優化建置速度` `優化建置` `Android Optimize Build` `optimize gradle build` `android optimize build`