匯總:Android小白成長之路_知識體系匯總【持續更新中…】
目錄
- 問題背景
- Gradle構建流程
- 優化前相關說明
- 優化方案
- 初始化速度優化
- 配置速度優化
- 執行速度優化
- 對Gradle進行配置
- 開啟并行編譯
- 增大編譯記憶體
- 開啟按需構建
- 開啟構建快取
- 開啟增量注解編譯
- 對AS進行配置
- 開啟離線模式
- 更改AS記憶體大小
- 更新最新Gradle版本
- Module原始碼轉aar
- 自定義執行的任務
- Maven代理
- 使用遠程共享構建快取
- 總結
問題背景
公司專案使用Android Studio以及Gradle進行編譯,在每次修改代碼(哪怕是一行修改),再次編譯運行都要耗時三四分鐘,甚至更長時間,在初次編譯時更是長達十幾分鐘、極大的影響了開發效率,俗話說工欲善其事,必先利其器,這就對編譯速度進行一波優化,讓我們一步一步開始吧!
Gradle構建流程
首先了解一下Gradle的構建流程,整體分為三個階段:
- 初始化階段:Gradle支持單專案和多專案構建,在初始化階段,Gradle從
setting.gradle中讀取需要參與構建的模塊,并為每個模塊創建一個Project實體, - 配置階段:配置專案模塊和其所需要執行的腳本,也就是
build.gradle等檔案 - 執行階段:開始執行配置后的腳本任務
大體上了解了這些流程,我們就可以從這些流程上入手進行優化
優化前相關說明
當前作為驗證的電腦相關資訊:
- 電腦名稱:MacBook Pro
- 系統:Mac
- 記憶體:8GB 1867MHz DDR3
- 處理器:雙核Intel Core i5 2.7GHz
- AS版本:4.1.1
驗證編譯速度的三個角度:
- rebuild全工程,全部編譯
- 新增一個方法,觸發java重新編譯
- 修改一個xml,觸發資源重新編譯
比較資料獲取方式:rebuild嘗試三次取最低值,修改方法或xml嘗試五次取最低值
相關說明:
- 由于電腦有時候卡頓或者別的原因影響編譯,會使得某次編譯耗時很長,因此不能取平均值作為參考
- 編譯一般會一次比一次快,因為Android studio自帶快取
- 開發階段本身就不會一直改配置,因此取最小值基本可以模擬日常使用情況
- 因為主工程模塊比較龐大,因此驗證時使用的是主工程模塊的代碼,如果修改的是組件代碼,用時一般會更少
- 每一次資料統計使用的方案繼承了它前面所有的優化方案
- 修改方法和xml用apply changes
優化方案
從整體構建流程可以得知,我們整體上需要從三個方面進行優化:
- 初始化速度優化
- 配置速度優化
- 執行速度優化
其中執行的程序占比是最大的,所以重心放在執行速度優化上
初始化速度優化
一般初始化程序任務較少本身就已經很快了,但仍然可以做一些處理,以達到最佳狀態:
- 當組件化程度較高時,在開發某個特定功能程序中有些組件是不需要引入的,此時可以在
setting.gradle中移除不需要引入的組件模塊,可以減少初始化時間 setting.gradle中include之前盡量不寫過多代碼
配置速度優化
配置階段主要是對各個build.gradle進行決議,因此可以注意以下幾點:
- 按需引入模塊,減少
build.gradle的決議 build.gradle中盡量少做耗時操作,例如讀取系統時間動態配置apk的名稱組成- 在開發階段不是必要執行的任務,可以寫判斷避免這些任務的配置,例如一些位元組碼插樁,性能監控之類的
執行速度優化
此階段存在的大量的任務需要執行,因此優化的點也非常的多
對Gradle進行配置
開啟并行編譯
開啟后會并行執行多個任務,大幅度減少編譯時間,只需要在gradle.properties中添加:
org.gradle.parallel=true
增大編譯記憶體
由于大家的電腦配置都不一樣,因此具體設定多大記憶體需要根據個人情況進行合理配置,一般在gradle.properties里已經有相關配置,可以對該配置進行修改,例如
org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
同時在主工程模塊的build.gradle中進行修改:
dexOptions {
javaMaxHeapSize "4g"
}
值得注意的是,javaMaxHeapSize的值需要比org.gradle.jvmargs設定的值少512m以上,而且org.gradle.jvmargs的值并不是設定越高越好,根據驗證,最好配置為系統記憶體的1/3,最多不要超過1/2,在部分檔案中顯示,高版本中javaMaxHeapSize中不再需要配置javaMaxHeapSize,只需要配置org.gradle.jvmargs即可,查閱了許多資料都沒說清楚,所以暫時都配置好了
開啟按需構建
對沒有更改的模塊不再進行編譯,非常適合已經組件化的專案,在gradle.properties中添加:
org.gradle.configureondemand=true
開啟構建快取
直接使用之前生成的快取,不再進行構建,在構建時任務后面會顯示FROM CACHE,在gradle.properties中添加:
org.gradle.caching=true
開啟增量注解編譯
支持注解增量編譯,不會重新觸發編譯(gradle高版本中需要移除),在gradle.properties中添加:
android.enableSeparateAnnotationProcessing=true
資料對比(并行編譯是優化前已經開啟,因此以下時間不包括并行編譯的優化):
| rebuild | 修改方法 | 修改xml | |
|---|---|---|---|
| 配置優化前 | 4m46s | 46s | 22s |
| 配置優化后 | 2m39s | 42s | 20s |
| 收益 | 減少44% | 減少8% | 減少9% |
對AS進行配置
開啟離線模式
開啟離線模式后不會再開始的時候去檢測依賴是否有更新,也不會去下載相關更新的依賴,首次構建不能開啟,否則無法完成構建,后續構建可以開啟,在某些情況下將大幅度改善編譯速度,強烈推薦開發階段使用,點擊下圖中的圖示的按鈕即可開啟離線模式,有些版本顯示為類似wifi的圖示,再次點擊取消離線模式:

更改AS記憶體大小
點擊AS的Help選單項,選中Change Memory Settings選項,如圖:

彈出如下圖彈框,把Maxinum Heap Size 修改為合適值,具體修改值根據自身電腦記憶體配置選擇

資料對比:
| rebuild | 修改方法 | 修改xml | |
|---|---|---|---|
| AS配置修改前 | 2m39s | 42s | 20s |
| AS配置修改后 | 2m16s | 37s | 16s |
| 收益 | 減少14% | 減少11% | 減少20% |
更新最新Gradle版本
由于gradle在新版本中一般都會對構建速度進行進一步的優化,因此保持最新的gradle版本可以獲得最佳的構建體驗,更新方式如下:
-
首先在
gradle-wrapper.properties中進行gradle版本的配置:distributionUrl=https\:``//services.gradle.org/distributions/gradle-6.7.1-all.zip -
然后在根目錄下的
build.gradle中更新gradle插件版本:classpath 'com.android.tools.build:gradle:4.1.1'
更新到6.x以上可能出現的問題和解決方案:
-
報例外:
FAILURE: Build failed with an exception. * What went wrong: A problem occurred configuring project ':live'. > Failed to notify project evaluation listener. > org.gradle.api.tasks.TaskInputs.property(Ljava/lang/String;Ljava/lang/Object;)Lorg/gradle/api/tasks/TaskInputs; > Could not get unknown property 'additionalParameters' for task ':live:dexBuilderDebug' of type com.android.build.gradle.internal.tasks.DexArchiveBuilderTask. * Try: Run with --info or --debug option to get more log output. Run with --scan to get full insights. * Exception is: org.gradle.api.ProjectConfigurationException: A problem occurred configuring project ':live'. at org.gradle.configuration.project.LifecycleProjectEvaluator.wrapException(LifecycleProjectEvaluator.java:75) ...... at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56) Cause 2: groovy.lang.MissingPropertyException: Could not get unknown property 'additionalParameters' for task ':live:dexBuilderDebug' of type com.android.build.gradle.internal.tasks.DexArchiveBuilderTask. at org.gradle.internal.metaobject.AbstractDynamicObject.getMissingProperty(AbstractDynamicObject.java:85) ...... at java.lang.Thread.run(Thread.java:748) * Get more help at https://help.gradle.org這是當前greenDao版本過低導致的,更新greenDao版本即可,在根目錄的
build.gradle下修改版本:classpath 'org.greenrobot:greendao-gradle-plugin:3.3.0' -
報例外:
FAILURE: Build failed with an exception. * Where: Build file '/Users/uxin/AndroidStudioProjects/Pika/UXLiveOverseas/live/build.gradle' line: 253 * What went wrong: A problem occurred configuring project ':live'. > Could not get unknown property 'additionalParameters' for task ':live:dexBuilderDebug' of type com.android.build.gradle.internal.tasks.DexArchiveBuilderTask. * Try: Run with --info or --debug option to get more log output. Run with --scan to get full insights. * Exception is: org.gradle.api.ProjectConfigurationException: A problem occurred configuring project ':live'. at org.gradle.configuration.project.LifecycleProjectEvaluator.wrapException(LifecycleProjectEvaluator.java:75) ...... at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56) Caused by: groovy.lang.MissingPropertyException: Could not get unknown property 'additionalParameters' for task ':live:dexBuilderDebug' of type com.android.build.gradle.internal.tasks.DexArchiveBuilderTask. at org.gradle.internal.metaobject.AbstractDynamicObject.getMissingProperty(AbstractDynamicObject.java:85) ...... at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56) * Get more help at https://help.gradle.org在
sdk 21之前,一般會使用第三方multidex的依賴開啟dex的分塊,而sdk 21之後,官方自帶multidex,因此需要去掉第三方的multidex:-
在主工程模塊的
build.gradle中,洗掉掉multidex的依賴和自定義任務://implementation 'androidx.multidex:multidex:2.0.0' // afterEvaluate { // tasks.matching { // it.name.startsWith('dex') // }.each { dx -> // if (dx.additionalParameters == null) { // dx.additionalParameters = ['--multi-dex'] // } else { // dx.additionalParameters += '--multi-dex' // } // } // } -
在自定義的Application類中洗掉
multidex的初始化://import androidx.multidex.MultiDex; //MultiDex.install(this);
-
-
報例外:
FAILURE: Build failed with an exception. * Where: Build file '/Users/xxx/Projects/xxx/xxx/xxx/build.gradle' line: 1 * What went wrong: A problem occurred evaluating project ':live'. > Failed to apply plugin 'com.android.internal.application'. > The option 'android.enableSeparateAnnotationProcessing' is deprecated. The current default is 'false'. It was removed in version 4.0 of the Android Gradle plugin. This feature was removed in AGP 4.0 * Try: Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights. * Get more help at https://help.gradle.org這是上面新添加的注解增量編譯欄位已經在gradle新版本中被移除了,所以應該去掉,在
gradle.properties中洗掉:#android.enableSeparateAnnotationProcessing=true -
報例外:
private static final String LIBRARY_VERSION = ". Version: " + BuildConfig.VERSION_NAME; ^ 符號: 變數 VERSION_NAME 位置: 類 BuildConfig由于
versionName和versionCode沒有多大差別,為了防止概念混淆,官方去掉了VERSION_NAME,因此我們專案中如果仍然需要用到,可與自定義buildConfig的VERSION_NAME,在報錯的模塊的build.gradle中配置:defaultConfig { minSdkVersion MIN_SDK_VERSION as int targetSdkVersion TARGET_SDK_VERSION as int versionCode 2 versionName "1.0.1" buildConfigField 'String', 'VERSION_NAME', "\"" + versionName + "\"" } -
報例外:
/Users/xxx/Projects/xxx/xxx/xxx/xxx/src/main/java/com/xxx/base/utils/Utils.java:86: 錯誤: 找不到符號 intent.putExtra(PAKAGENAME, BuildConfig.APPLICATION_ID); ^ 符號: 變數 APPLICATION_ID 位置: 類 BuildConfigGradle高版本中為了防止library里使用
BuildConfig.APPLICATION_ID導致id和appication本身的id理解混淆,需要改為新的欄位:BuildConfig.LIBRARY_PACKAGE_NAME -
報例外:
/Users/xxx/Projects/xxx/xxx/xxx/xxx/src/main/java/com/xxx/base/view/ShareScreenShotDialog.java:205: 錯誤: 找不到符號 shareInfo.setWeiboCopyWriter(String.format(mContext.getString(R.string.novel_share_intro_wb_empty), ^ 符號: 變數 novel_share_intro_wb_empty 位置: 類 stringGradle高版本不允許語言配置中默認語言配置為空,所以需要在
default string中添加上報錯的那部分string -
報例外:
Execution failed for task ':live:transformClassesWithAjxForRelease'. > Cannot cast object 'com.android.build.gradle.internal.pipeline.TransformTask$2$1@6fe77eee' with class 'com.android.build.gradle.internal.pipeline.TransformTask$2$1' to class 'com.android.build.gradle.internal.pipeline.TransformTask'這是由于
aspectjx版本過低導致,更新版本即可,在根目錄的build.gradle中修改:classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.10'
資料對比:
| rebuild | 修改方法 | 修改xml | |
|---|---|---|---|
| Gradle更新前 | 2m16s | 37s | 16s |
| Gradle更新后 | 1m15s | 33s | 12s |
| 收益 | 減少44% | 減少10% | 減少25% |
Module原始碼轉aar
隨著業務量的增大,module的引入也會增多,每個module在編譯的時候都需要花費一定的時間,即使新版本gradle對未修改并具有快取的module不進行編譯,但取快取也需要一定時間,把module轉化成aar后就不再需要每次都進行編譯或者取快取,可以減少一部分時間
Module轉aar優化步驟如下:
-
對每個module進行
Build->Make module xxx生成aar在build/output/aar中 -
新建一個module,隨意取名字,把其他module生成的aar復制到新建module的libs下,同時把其他module的libs下的aar包也同樣復制到新建module的libs下(因為aar包不會包含自己依賴的其他aar包),也可以不新建module,直接放到主工程的libs下
-
把新建module的src下其他檔案和檔案夾洗掉,只留下main檔案夾和
AndroidManifest.xml檔案 -
把
AndroidManifest.xm里的application洗掉 -
在新建module的
build.gradle中把其他module所參考的aar依賴全部復制過來,同時依賴上其他幾個module制作的aar -
在根目錄的
build.gradle中修改:flatDir { dirs 'libs',project(':aar的module').file('libs') }
用一個新的module來存放aar的好處:
- 和主工程模塊隔開,不需要把aar都復制到主工程的libs,也不需要把依賴寫在主工程模塊的依賴中
- 可以在
setting.gradle中直接判斷選擇aar編譯還是原始碼編譯 - 某個組件更新了可以編譯一下直接替換aar檔案,即時更新
擴展:
可以做一個全域變數控制使用組件原始碼或者aar,操作步驟如下:
-
在
setting.gradle中新增全域開關#是否修改組件代碼 isModifyInComponent=false -
在主工程模塊的build.gradle中修改:
def modifyInComponent = isModifyInComponent.toBoolean() flatDir { if (modifyInComponent) { dirs 'libs', project(':原始碼的module').file('libs'), }else { dirs 'libs',project(':aar的module').file('libs') } } if (modifyInComponent) { implementation project('原始碼的module') } else { implementation project('aar的module') } -
在
setting.gradle中修改:include ':app if (isModifyInComponent.toBoolean()) { include ':原始碼的module' } else { include ':aar的module' }
最好的方式是搭建私服maven倉庫用來存放aar,直接依賴就完成了,更加方便而且容易管理,這里后續再寫相應的文章
資料對比:
| rebuild | 修改方法 | 修改xml | |
|---|---|---|---|
| 組件原始碼 | 1m15s | 33s | 12s |
| 組件aar | 50s | 28s | 11s |
| 收益 | 減少33% | 減少15% | 減少8% |
自定義執行的任務
在構建程序中,有部分task是為了優化app而執行的,這些task在開發程序中并不需要執行,只需要在正式打包的時候執行即可,因此可以暫時關閉這些任務,以減少執行時間,可以引入一個全域變數當做開關,然后在這些任務插件引入的地方做判斷,按需執行,
操作步驟如下:
-
在
gradle.properties中定義一個開關#開啟快速編譯模式,快速編譯舍棄了一些配置,可以較快編譯執行app,適合開發除錯階段 isFastBuildMode=false -
在主工程模塊的
build.gradle中做判斷,例如下面的:def fastBuildMode = isFastBuildMode.toBoolean() if (fastBuildMode) { repositories { flatDir { dirs 'libs', project(':aar的module').file('libs') } } } else { apply plugin: 'org.greenrobot.greendao' apply plugin: 'walle' apply plugin: 'com.didiglobal.booster' apply plugin: 'android-aspectjx' repositories { flatDir { dirs 'libs', project(':原始碼的module').file('libs') } } greendao { schemaVersion 2 targetGenDir 'src/main/java' } walle { ... } aspectjx { exclude 'com.alipay', 'com.tencent', 'com.squareup.leakcanary' } } if (fastBuildMode) { ndk { abiFilters 'armeabi-v7a' } resConfigs "cn", "xhdpi" } else { ndk { abiFilters 'armeabi-v7a', 'arm64-v8a' } }
注意:開啟快速編譯開關會關閉一些任務,所以可能會導致一些不可預知的問題,如果除錯程序中出現例外,可以關閉快速編譯開關重新嘗試,在打包apk發布的時候一定要記得關閉快速編譯開關
資料對比:
| rebuild | 修改方法 | 修改xml | |
|---|---|---|---|
| 關閉快速編譯 | 50s | 28s | 11s |
| 開啟快速編譯 | 38s | 13s | 11s |
| 收益 | 減少24% | 減少53% | 持平 |
Maven代理
前面說過可以創建私服maven用來存放生成的aar,其實也可以用私服maven來代理需要下載的依賴,放在內部網路倉庫中,需要的時候直接從內網中讀取,而無需去遠程maven倉庫讀取,這對一些使用外網的maven倉庫的專案具有非常大的幫助,如果不想自己搭建,也可以使用阿里云的鏡像maven倉庫,里面有常用的一些倉庫鏡像,搭建方法后續更新一篇文章來說明
使用遠程共享構建快取
前面提到過開啟快取的方式,但是那只針對于本地快取,在首次編譯時,快取為空,仍需要大量的時間進行編譯,但是在公司開發程序中,通常已經有同事的機器或者CI構建已經進行了編譯構建,我們能不能用某種方式來使用他們的快取呢?這樣就解決了首次編譯時間過長的困境,辦法總比困難多,對共享構建快取感興趣的可以去查看這篇文章:Gradle使用遠程構建快取
總結
比較安全簡單優化方案:
- 開啟并行編譯、按需構建、構建快取
- 開啟注解增量編譯(Gradle插件4.0以下)
- 開啟離線模式
- 使用apply changes
- 修改JVM大小
需要適配的優化方案:
- 升級gradle及其插件
- 使用官方muitidex
只適合除錯開發階段的極速方案:
- module轉aar
- 按需自定義執行任務
更深入的優化:
-
自定義撰寫優化插件,提高快取命中率等
-
私服maven鏡像代理
-
CI共享構建快取
Mac系統總收益:
| rebuild | 修改方法 | 修改xml | |
|---|---|---|---|
| 優化前 | 4m46s | 46s | 22s |
| 優化后 | 38s | 13s | 11s |
| 收益 | 減少86% | 減少46% | 減少50% |
Windows 系統驗證
配置:
- 處理器:i7-10510U 2.3GHz
- 記憶體:8GB
- 固態硬碟
總收益:
| rebuild | 修改方法 | 修改xml | |
|---|---|---|---|
| 優化前 | 10m30s | 2m33s | 12s |
| 優化后 | 1m9s | 12s | 9s |
| 收益 | 減少89% | 減少92% | 減少25% |
資料可能不完全準確,但編譯速度是肉眼可見地飛升,優化后可以大幅度減少編譯時間,這時間拿去喝咖啡它不香嘛,如果經費充足,再更新一波電腦配置,速度直接起飛,少加班就靠這個優化了!
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/302815.html
標籤:其他
