Android Studio編譯慢、卡死和狂佔內存怎麼破?

做者:Ailurus
連接:https://www.zhihu.com/question/27953288/answer/118031242
來源:知乎
著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。
 html

至於加快編譯速度,有一句說一句,我覺着一些答主的答案適用性都並不強,其實仍是應該從 gradle 入手,講的有什麼不合適的地方,還請輕噴,有什麼問題也能夠留言。java

如下我講到的全部步驟,推薦都在終端裏執行。在終端裏執行編譯有不少好處:android

  1. 能夠觀察到整個編譯過程,有助於理解 gradle 構建流程;
  2. 能夠看到編譯過程當中哪些任務比較耗時,能夠對編譯慢的問題對症下藥;
  3. 能夠隨時終止編譯,若是被卡在某個階段,ctrl + c 能夠隨時終止編譯,在 Android Studio 裏也終止編譯,可是基本上十次有九次都會失敗;
  4. 由於是在終端裏,對 Android Studio 影響極小,基本不會形成 Android Studio 卡頓;
  5. 不會遇到 Android Studio 的各類 bug 。


先說一下 gradle 的生命週期吧,gradle 構建一個工程主要分爲三部分(徹底掌握了下面這張圖,整個 gradle 的構建過程能瞭解個十之七八了):git

  1. 初始化階段:主要是解析 setting.gradle 文件(所以有人提到減小 setting.gradle 的 module 數量,是頗有道理的,可是實際操做過程限制頗多,緣由最後會大體說一下);
  2. 讀取配置階段:主要是解析全部的 projects 下的 build.gradle 文件,包括 rootProject 和其餘的 subprojects(子項目),檢查語法,肯定 tasks 依賴以創建 task 的有向無循環圖,檢查 task 裏引用的文件目錄是否存在等(這一步也進一步驗證了減小 setting.gradle 裏的 module 數量能夠加快編譯速度,由於減小一個 module ,須要解析的 build.gradle 文件就減小一個,第 3 步裏就不會執行本屬於這個 module 的任務了,可是仍是 1 裏面說的問題,限制頗多);
  3. 執行階段:按照 2 中創建的有向無循環圖來執行每個 task ,整個編譯過程當中,這一步基本會佔去 9 成以上的時間,尤爲是對於 Android 項目來說,將 java 轉爲 class
     
    compileDebugJavaWithJavac/compileReleaseJavaWithJavac
    和 將 class 合併成 dex
     
    transformClassesWithDexForDebug/transformClassesWithDexForRelease
    這兩步很耗時,第一步還好,第二步會耗時很是久。首先在 gradle.properties 裏設置
     
    org.gradle.jvmargs=-Xmx4096m //越大越好
    ,而後在工程的 build.gradle 裏的 android 結點下增長 dexOptions 配置,以下:
dexOptions {
    dexInProcess true
    preDexLibraries true
    javaMaxHeapSize "4g"//越大越好
    incremental true
}


明確了 gradle 的生命週期,那麼就能夠看到加快編譯速度的關鍵就是從第三步入手,固然,減小 setting.gradle 裏的 modules 數量這一步也是必須的。下面說說咱們公司的實踐吧。github

  1. 項目插件化改造,每位業務上的同窗只須要編譯一個模塊便可,這一點基本上從根本上解決了編譯慢的問題(對於大多數沒有插件化需求的朋友們能夠看下面的一些實踐),首先 setting.gradle 裏的 module 只有本身開發的模塊了,而對應的執行階段的任務也只有這一個 module 的任務了。
  2. 執行一次 gradle build ,咱們就會發現,在這個過程當中,實際上是執行了屢次打包任務的,在 buildTypes 裏配置了多個編譯打包類型,默認有 debug 和 release ,咱們還能夠手動配置其餘的類型,並且還有 productFlavor 裏的多渠道,這樣就會執行屢次編譯打包,而正常開發過程當中,只須要打 debug 包去調試,所以使用 gradle assembleDebug 便可,等發版的時候使用其餘方式去打多渠道的包(如美團的方案http://tech.meituan.com/mt-apk-packaging.html);
  3. 既然編譯主要時間都集中在 gradle 生命週期的第三步執行 task 任務裏,那麼咱們就能夠把一些可有可無的任務給禁用掉,好比各類 Test ,各類 lint 等,恰好在 gradle 裏有這樣的指令 -x lint 能夠臨時禁掉 lint 任務,-x test 能夠禁掉 test 任務,事實上對於一個稍微大一點的項目,lint 也是很耗時的,固然也能夠經過 gradle 腳本完全禁用 lint 和 test 任務,我也在一些微信羣裏分享過相關代碼,可是不太建議這麼作,由於有時候 lint 和 test 也是挺有用的;
  4. gradle 自己提供了一些指令參數能夠加快編譯,好比 --daemon ,開啓守護進程,--parallel ,開啓並行編譯等,這個也能夠在 gradle.propertites 裏配置(編譯使用的 jvm 內存也能夠在這裏配置)。
  5. 定製 gradle 編譯流程,利用官方提供的 API 徹底能夠定製一個適合本身的編譯流程,能夠參考一下攜程的 DynamicAPK/sub-project-build.gradle at master · CtripMobile/DynamicAPK · GitHub,裏面有攜程他們本身整個完整的編譯流程,腳本自己很簡單,一共只有兩三百行代碼。

上面講到的幾點,現有環境就能夠作到的大概是這樣(有一點要特別注意,若是工程裏有交叉依賴,必定不要使用 --parallel 參數):微信

gradle assembleDebug --daemon --parallel -x lint -x test

,若是是要直接安裝到設備上的話,就把 assembleDebug 換成 installDebug ,assembleDebug 能夠簡寫爲 asD ,installDebug 能夠簡寫爲 iD 。jvm

最後講一下,爲何減小 setting.gradle 裏的 module 數量,確實能夠加快編譯,可是卻限制頗多呢?
首先,咱們想一下整個編譯過程,先去解析 gradle 配置,創建 tasks 依賴有向圖,而後再去執行每個 module 的 task ,若是咱們經過 maven 依賴,使用 aar 替掉了 module(單指 android library),若是咱們要改這個 module 裏的文件,豈不是每次都要修改上傳再下載,這其實還好,可是有一個致命的問題:不修改版本號的話,SNAPSHOT 在 IDEA 裏常常會很差使這樣就致使修改的東西會不生效,去解決這個問題是很是耗費時間的。不過有一種方式,能夠必定程度上解決問題,增長下面的腳本:maven

project.configurations.all(new Action<Configuration>() {
@Override
    void execute(Configuration files) {
        files.resolutionStrategy.cacheDynamicVersionsFor(5, TimeUnit.MINUTES)
        files.resolutionStrategy.cacheChangingModulesFor(0, TimeUnit.SECONDS)
    }
})

那有人會問,插件化裏,每一個人開發一個模塊,對於每一個模塊的維護不也是要打包上傳到 maven ,每次一有修改,哪怕是很是微小的修改,也要作一次上傳,一樣會遇到 SNAPSHOT 很差使的問題。嘿嘿,這個問題嘛,我司本身維護了一個 gradle 插件,已經解決了,至於解決方案,是公司機密,我是不會講的。
而後,還有一點,我相信大部分開發者日常開發都是單 module 的,多 module 的狀況並很少,所以大多數依賴基本也都是 aar 或者 jar ,根本就不存在所謂的將 library 轉成 aar 上傳的狀況,所以一些答主說的根本毫無心義,這也是爲何我會說影響編譯速度的狀況主要集中在 gradle 生命週期的第三個階段,至於第三個階段的優化,看我上面的答案就行了。ide

相關文章
相關標籤/搜索