提高 50% 的編譯速度!阿里零售通 App 工程提效實踐

前言

當前,大多數 Android 工程都是基於 Gradle 工具進行構建和編譯的,一開始,當你的工程不夠複雜,或者還只是小型項目的狀況下,基本都不須要去關心構建優化的事情,而隨着業務變得複雜、代碼量的增多以及愈來愈多的依賴,原有的 單 module 工程變成了多 module 工程,構建時間變得也愈來愈多。html

說到這裏,有的同窗可能會有疑惑,對於大項目來講,這麼多模塊和依賴,原本就須要更多的編譯時間,還怎麼減小構建時間?偏偏相反,實際上越大的項目越能省出來時間。前端

爲了讓開發者引發對構建分析的重視,Gradle 官方在最近的版本更新中推出了一個神器 build scan,可視化的深刻分析和診斷全部構建相關的數據,並基於此分析結果幫助開發者找出構建問題以及針對構建性能進行優化。android

背景

零售通買家端 App 是面向小店的一站式採購平臺,能夠快速幫助小店老闆經過手機完成進貨等功能。隨着業務的不斷髮展,咱們的工程規模和代碼量也獲得了極大的發展,目前咱們的客戶端工程裏的 module 數量達到了40多個,而涉及到的相關依賴庫則有 200 多個,已然成爲一箇中大型項目,能夠想象每次編譯的時候,我和個人小夥伴們是多麼痛苦。後端

問題

  • 編譯速度慢到懷疑人生,通常,咱們的項目正常編譯時間大概在每次 3 分多鐘,若是中間連續編譯好幾回,時間也會相應成倍增長,同時,電腦 cpu 運轉的聲音也會愈來愈響,會有種錯覺是在小霸王學習機上進行開發,印象裏最深的有一次編譯花了十幾分鍾時間。固然,後面變聰明瞭,只要聽到小霸王學習機的聲音,就意味着能夠重啓電腦了。緩存

    筆者電腦配置:公司標配 MacBook Pro,16G 內存,i7 處理器性能優化

  • 莫名其妙的編譯問題,沒法經過 IDE 的 Run按鈕運行,必需要執行命令行編譯 ./gradlew clean assembleDebug,原本時間就很慢了,加上 clean 命令後,更是雪上加霜。網絡

  • module 找不到間接依賴庫的類,中間有一次升級 Android Studio 版本後,工程裏的 module 裏的間接依賴庫裏的類都找不到了,直接飆紅了。不過,並不影響正常編譯和運行。緣由是 *.iml 文件裏的沒有自動生成間接依賴的 library 致使的。應該是 Android Studio 和 Gradle 低版本兼容問題致使,後面升級到最新版本後解決。app

以上編譯相關的問題,幾乎天天都在消耗着我和個人小夥伴的寶貴時間,也嚴重影響了平常的開發效率,因此,當看到多個小夥伴在長時間編譯後受不了而投來的幽幽眼神後,我想,是時候開始解決這些問題了。ide

解決

1. 升級 Gradle 版本

在開始任何分析優化工做前,第一件事情是升級你的 Gradle 版本,這也是最簡單見效的方法,新的版本,一般意味更好的性能和特性,這也是一條來自 Gradle 官方的優化建議工具

A guide on performance tuning would normally start with profiling and something about premature optimisation being the root of all evil. Profiling is definitely important and the guide discusses it later, but there are some things you can do that will impact all your builds for the better at the flick of a switch. Use latest Gradle and JVM versions, The Gradle team works continuously on improving the performance of different aspects of Gradle builds.

可是,對於咱們的工程來講,升級 Gradle 版本並非一件輕鬆的事情,咱們有專門的內部打包平臺,並引入了它的打包插件,而內部打包插件強依賴了低版本的 Gradle 。雖然,內部打包插件也提供了基於新版本的插件,可是,嘗試升級後產生了一些插件兼容問題,並且,後續咱們也沒法跟隨官方的 Gradle 版本進行升級,考慮到一般只有在發版本或者集成測試的時候纔會使用內部平臺打包。因而,咱們採用了另外一種方式,經過腳本的控制,讓本地平常開發和編譯使用新版本的 Gradle,內部平臺打包依然走老版本的 Gradle 打包

在升級 Gradle 和 Android Gradle Plugin 版本後,所帶來的編譯時間的提高很是明顯。因此,若是你使用的 Gradle 版本越低,那麼升級新版本後,所帶來的提高也越明顯。

2. 優化和減小 module

咱們回顧下 Gradle 的構建生命週期:

  • 初始化階段: 在初始化階段,支持單個或者多項目構建,它決定哪些項目模塊要參與構建,併爲每一個項目模塊建立一個工程實例。
  • 配置階段: 在這一階段,一般是配置每一個項目模塊。 並執行全部項目模塊的構建中的一部分腳本。
  • 任務執行階段: 在初始化和配置階段執行完成後,Gradle 開始執行每一個參與的任務。

Gradle 提供了一個 --profile 命令,來幫助咱們瞭解在每一個階段所花費的時間,並生成一個報告。好比,你能夠執行如下命令來獲取到一份報告,位於 rootProject/build/report/profile/***.html

./gradlew assembleDebug --profile
複製代碼

上圖是在咱們項目執行的命令後生成的報告,總構建時間花了 3分多種。

  • Summary:構建時間概要
  • Configuration:配置階段花費的時間
  • Dependency Resolution:依賴解析花費的時間
  • Task Execution:每一個任務執行的時間,也是耗時最多的階段

Tips:Summary 概要裏的 Task Execution 時間是每一個模塊累計相加,實際上多模塊的任務是並行執行的。

Task Execution 裏是每一個模塊編譯所花費的具體時間。同時,也能夠看出編譯一個 module 的成本仍是比較大的,由於有不少 task 須要執行。因此,接下來的工做就是減小和優化這些的模塊。

在從新梳理了下咱們的項目模塊後,發現有一部分 module 裏面其實就只有兩三個類,徹底沒有必要單獨 module,能夠轉移到 app 或者 common-business 模塊裏,而有一些 module 裏的核心邏輯已經抽成了獨立 aar 引用,針對殘留的代碼邏輯,則進行了優化。在完成這些工做後,咱們的工程模塊數量由 40 多個減小到了 20 多個,是的,我幹掉了將近一半的 module。

建議:能不建 module,就不要新建 module,若是確實須要,可使用 aar 方式代替,aar 編譯是有緩存的話,理論上應該比 module 要快的,具體我沒有對比過。有興趣的同窗,能夠實際對比下試試

3. 一些配置優化

  • 增長 snapshot 緩存策略開關,有時候,爲了 snapshot 版本的變更能夠實時生效,會加上配置 cacheChangingModulesFor 0, 'seconds',可是,這樣就會在每次編譯都要去雲端比對是否有變更。因此,你能夠經過在 local.properties 增長開關來控制,在不須要的時候,關閉它。

    configurations.all {
          resolutionStrategy {
              if (rootProject.ext.cacheChangingModulesForDisable == false) {
                  cacheChangingModulesFor 0, 'seconds'
                  //針對  dynamic (例如2.+)的配置同理
                  //cacheDynamicVersionsFor 0, 'seconds'
              }
          }
      }
    複製代碼
  • 避免編譯沒必要要的資源,好比沒必要要的語言本地化

    android {
         ...
         productFlavors {
           dev {
             ...
             // 只編譯如下語言和分辨率的資源
             resConfigs "zh", 'zh-rCN', "xxhdpi"
           }
           ...
         }
       }
    複製代碼
  • 不一樣的 Gradle 版本,一些配置優化也會有區別,更多配置優化能夠參考 Gradle 提速:天天爲你省下一杯喝咖啡的時間

4. 其餘優化

  • 腳本邏輯優化,如避免使用一些網絡請求或者 IO 操做。
  • 去除重複和沒必要要的依賴

結果

好了,通過以上種種努力,終於到了收穫的時刻了。由於 Gradle 每次編譯的時間偏差仍是比較大的,爲了儘量保證比對結果的客觀性,咱們使用如下命令分別在優化前和優化後進行了 3 次編譯:

gradlew --profile --recompile-scripts --offline --rerun-tasks assembleDebug
複製代碼
  • --recompile-scripts: 繞過緩存,強制從新編譯腳本
  • --rerun-tasks: 強制從新運行全部 task,忽略任何優化
  • offline: 離線模式編譯
構建次數 優化前 優化後 減小率
第一次 3分20秒 1分59秒 - 40%
第二次 2分50秒 1分7秒 - 60%
第三次 2分40秒 54秒 - 67%

以上數據能夠看到,在優化後的工程裏,編譯次數越多,編譯時間越少,實際所提高的速度也遠超過 50%。

並且,升級版本後,終於能夠經過 Run 按鈕編譯了,最快的一次增量編譯時間只有 9s !

總結

評論中 明朗同窗 理解和總結的很到位,我就直接貼上來了,感謝~

  1. 升級到最近的gradle 版本 並採用最新語法 好比implementation 代替compile等
  2. 代碼層面的優化 好比減小module 使用 aar 依賴
  3. 其餘的一些優化配置等

感想

雖然咱們的編譯時間減小到了 1 分多鐘,但仍然有較大優化空間,好比爲了兼容老的 Gradle 版本,目前咱們尚未辦法使用 implementation 替換 compile,這樣,就能夠有效的減小編譯時的依賴項,另外,後續也會藉助於 build scan 去更深刻分析和了解咱們的構建信息。

若是你所在的項目,構建編譯時間成了一種負擔,那麼,頗有必要引發你的重視。也許,每一個項目的實際狀況可能都不同,可是,但願本文能夠爲你提供一些思路,我相信,隨着你的深刻分析以後,必定會有更好的辦法去解決,另外,重要的是,不多有這樣的性能優化,能夠在短期內帶來實際的提高效果,因此,如今,馬上開始你的構建優化吧。

若是,對構建優化有什麼問題或者不瞭解的地方,歡迎留言討論。

參考

Gradle 提速:天天爲你省下一杯喝咖啡的時間

最後

一條簡短的廣告

零售通是阿里巴巴五新戰略之一,也是探索新零售模式的一隻創業大軍,在業務快速發展的同時,咱們所面臨的技術挑戰也愈來愈高!服務每家店,只爲每一個家是咱們的使命,爲了更好的服務咱們的使命,咱們須要大量的客戶端(Android & iOS)、前端、後端同窗,期待您和咱們一塊兒戰鬥!

簡歷請投:yonglan.whl@alibaba-inc.com

雙十一來臨之際,奉上團隊風采照一張

PS:本廣告長期有效

相關文章
相關標籤/搜索