轉:GRADLE構建最佳實務

來源:互聯網
上載者:User

標籤:

轉自:

http://www.figotan.org/2016/04/01/gradle-on-android-best-practise/#section-2

 

隨著Google對Eclipse的無情拋棄和對Android Studio的日趨完善,使用gradle構建Android項目已經成為開發人員的一項必會良技。那麼,問題來了,採用什麼樣的姿勢才能讓項目開發構建過程高潮迭起,精彩不斷呢?
其實網上有很多關於gradle的文章,gradle官方和Google也提供了詳細的文檔和教程,可素,當你在構建過程中遇到一些問題或者有特殊的愛好(需求)的時候,這些東西未必能幫(mei)上(shen)什(me)麼(niao)忙(yong),然後就是一頓FQ找Google蜀黍約約約,去stackoverflow上各種搜刮問大神,最後解決了。即使沒有真的解決那麼就忍了。

那怎麼行?是可忍孰不可忍,奇技淫巧必須有。所以就會有這樣一篇文章,我在這裡不講原理,因為我知道很多人明白辣麼多的底層原理,仍然擼不上好代碼,做不成好項目,出不了好產品,自然也就過不好這一生咯。
我們先從GRADLE構建的時間花銷開始談起。

加速篇

GRADLE的構建過程通常會比較漫長,一個中等項目,10M左右大小的app,一次完整構建大概在5分鐘左右,是不是很嚇人,當然,如果是在調試階段,採用Android Studuo 2.0,預設提供的Instant Run方式,每次修改都不會重新構建項目,從而加快了構建過程。恩,這是另一個故事,這裡,我們先談談GRADLE指令碼的加速姿勢。
一般來說,GRADLE一次完整的構建過程通常分成三個部分,初始化,配置和執行任務,那麼我們可以考慮從這三個部分分別嘗試最佳化。

使用daemon

構建初始化的很多工作是關於java虛擬機器的啟動,載入虛擬機器環境,載入class檔案等,如果這些動作交給一個單獨的後台進程去做,那麼,第一次初始化之後的修改代碼再構建是不是可以節省很多時間呢?答案是肯定的,通過在gradle.properties加入這樣一句來開啟,如果想讓修改全域所有項目都生效,那麼修改這個檔案~/.gradle/gradle.properties

org.gradle.daemon=true
按需配置

配置有一種方式是按需配置,目前還在實驗孵化階段,所以預設是關閉的,可以通過在gradle.properties加入這樣一句來開啟

org.gradle.configureondemand=true
依賴庫使用固定版本

項目開發過程中,難免需要用到三方庫,這就形成了項目之間的依賴關係,GRADLE提供了多種整合三方庫的方式,提供了很方便的項目依賴管理,本地庫,庫工程,maven庫全支援。既然用到庫,就會遇到庫版本的問題和升級問題,其中maven庫的依賴管理支援一種動態版本的方式,也就是說,GRADLE可以做到不依賴具體某個版本的庫,而是每次從repo拉取最新的庫到本地做編譯。具體使用是這樣的:
拿gson庫舉例,如果依賴2.2.1這個版本,可以在build.gradle檔案裡這樣寫

dependencies {compile ‘com.google.code.gson:gson:2.2.1‘}

如果不想依賴具體的庫,想每次從maven repo中拉取最新的庫,那麼,可以寫成這樣:

dependencies {compile ‘com.google.code.gson:gson:2.2.+‘}

也可以寫成這樣

dependencies {compile ‘com.google.code.gson:gson:2.+‘}

甚至可以這樣

dependencies {compile ‘com.google.code.gson:gson:+‘}

其中含義相信不用我解釋,大家也看得明白吧。
用”+”來通配一個版本族,這樣有個好處是maven上有新庫了,不用你操心升級,GRADLE編譯的時候自動升級了,但是帶來了兩個壞處,一是,有可能新版庫的介面改了,導致編譯失敗,這個時候需要修改代碼做升級適配;更大的壞處是,每次GRADLE編譯完整的項目,都會去maven上試圖拉取最新的庫,這樣,拖慢了編譯速度,尤其在網路非常差的時候,所以,為了構建速度,建議寫死依賴庫的版本號碼。

升級到最新的GRADLE和JDK

有一個很通俗的道理是,發展的東西會越來越好,最新版的GRADLE和JDK往往是效能最好,運行最流暢最快的,所以,升級吧,JDK的升級這裡不說了,具體看Oracle的官方文檔。這裡說說GRADLE的版本升級,GRALDE採用了一種叫做wrapper的方式,可以做到每個項目獨立使用其自己的GRADLE版本,這樣做的好處不言而喻,每個項目的構建環境獨立,互不影響。但為什麼會出現這個東西,我的猜想是因為GRADLE發展太快,新舊版本之間很難相容。如果你有多重專案都採用GRADLE構建,假設都用同一個全域的GRADLE,那麼當這個GRADLE升級後,所有的項目可能都會編譯失敗,你得一個一個改配置,那麼,下次再升級,同樣的流程的再走一遍,是不是很煩。採用wrapper的方式很好的解決了這個問題,每個項目採用獨立的GRADLE版本,互不影響,如果你只想升級其中一個,你改這一個項目的GRADLE wrapper就好了。在你的項目目錄下找到這個檔案gradle/wrapper/gradle-wrapper.properties並修改distributionUrl=https://services.gradle.org/distributions/gradle-2.11-all.zip到你想升級的版本就可以了。

減少編譯指令碼中的I/O操作

有時候,編譯指令碼中會有一些代碼做動態資訊的擷取,比如想從git中擷取一個數字作為版本號碼

def cmd = ‘git rev-list HEAD --first-parent --count‘def gitVersion = cmd.execute().text.trim().toInteger()android {  defaultConfig {    versionCode gitVersion  }}

其實這個操作主要是為了在構建的機器上為了發布版本而做的,日常環境研發調試無需這樣,所以可以最佳化成如下方式:

def gitVersion() {  if (!System.getenv(‘CI_BUILD‘)) {    // don‘t care    return 1  }  def cmd = ‘git rev-list HEAD --first-parent --count‘  cmd.execute().text.trim().toInteger()}android {  defaultConfig {    versionCode gitVersion()  }}
並行構建模組化項目

將你的項目拆分成多個子項目並開啟並行構建也是一個不錯的主意,比如將相對獨立的模組拆分成獨立的庫工程(Library projects),主工程(Application project)依賴這些庫工程,這樣的話,開啟並行構建才會發揮作用。並行構建開啟方式是修改檔案gradle.properties,加入如下行:

org.gradle.parallel=true
基礎配置篇

全域基礎組態管理
#### 設定全域編碼 如果匯入一個windows下編寫的項目,而代碼中有中文注釋,採用GBK, GB18030等編碼方式時,編譯會報錯,可以採用如下方式統一項目的編碼

allprojects {    repositories {        jcenter()    }    tasks.withType(JavaCompile) {        options.encoding = "UTF-8"    }}
設定全域編譯器的版本

如果編程過程中採用了新版JDK(比如1.7)才支援的特性(比如new HashMap<>這樣的寫法),而編譯的時候預設是舊版的JDK(比如1.6),這個時候編譯會報錯,採用如下方式可以指定用哪個版本的編譯器編譯,前提是JAVA_HOME指定的JDK是大於等於新版JDK的哦^o^,其他和java編譯器相關的也可以在這裡配置

allprojects {    repositories {        jcenter()    }    tasks.withType(JavaCompile) {        sourceCompatibility = JavaVersion.VERSION_1_7        targetCompatibility = JavaVersion.VERSION_1_7    }}

如果不想全域生效,可以將tasks.withType(JavaCompile)放入某個子項目中。

配置簽名資訊

簽名資訊屬于敏感資訊,建議不要寫死放到gradle指令碼中,而是寫到一個單獨的設定檔裡,而且這個設定檔不要同步到版本管理系統上,而是由本地維護,防止在版本管理平台上泄漏敏感資訊。建議簽名資訊內容寫到gradle.properties或者local.properties檔案裡,這樣,gradle指令碼可以直接引用,如果是放在一個自訂的檔案中,gradle指令碼需要提供相應的代碼來讀取檔案的內容。 檔案內容參考如下:

RELEASE_KEY_PASSWORD=androidRELEASE_KEY_ALIAS=androidreleasekeyRELEASE_STORE_PASSWORD=androidRELEASE_STORE_FILE=../resources/release.keystoreDEBUG_KEY_PASSWORD=androidDEBUG_KEY_ALIAS=androiddebugkeyDEBUG_STORE_PASSWORD=androidDEBUG_STORE_FILE=../resources/debug.keystore

gradle指令碼引用代碼參考:

android {    signingConfigs {        debug {            storeFile file(DEBUG_STORE_FILE)            storePassword DEBUG_STORE_PASSWORD            keyAlias DEBUG_KEY_ALIAS            keyPassword DEBUG_KEY_PASSWORD        }        release {            storeFile file(RELEASE_STORE_FILE)            storePassword RELEASE_STORE_PASSWORD            keyAlias RELEASE_KEY_ALIAS            keyPassword RELEASE_KEY_PASSWORD        }    }}

如果簽名資訊沒有放到gradle.properties或者local.properties檔案裡,那就需要自己寫代碼讀取咯,假設簽名資訊放在signing.properties檔案中, 檔案內容可以參考上面,讀取檔案的代碼放入gradle指令碼中就可以了,參考代碼如下

def File propFile = new File(‘signing.properties‘)if (propFile.canRead()) {    def Properties props = new Properties()    props.load(new FileInputStream(propFile))    if (props != null && props.containsKey(‘RELEASE_STORE_FILE‘) && props.containsKey(‘RELEASE_STORE_PASSWORD‘) &&            props.containsKey(‘RELEASE_KEY_ALIAS‘) && props.containsKey(‘RELEASE_KEY_PASSWORD‘)) {        android.signingConfigs.release.storeFile = file(props[‘RELEASE_STORE_FILE‘])        android.signingConfigs.release.storePassword = props[‘RELEASE_STORE_PASSWORD‘]        android.signingConfigs.release.keyAlias = props[‘RELEASE_KEY_ALIAS‘]        android.signingConfigs.release.keyPassword = props[‘RELEASE_KEY_PASSWORD‘]        println ‘all good to go‘    } else {        android.buildTypes.release.signingConfig = null        println ‘signing.properties found but some entries are missing‘    }} else {    println ‘signing.properties not found‘    android.buildTypes.release.signingConfig = null}
設定第三方maven地址

其中name和credentials是可選項,視具體情況而定

allprojects {    repositories {        maven {            url ‘url‘            name ‘maven name‘            credentials {                username = ‘username‘                password = ‘password‘            }        }    }}
GRADLE指令碼拆分以及引用

如果一個gradle指令碼太大,可以按照具體任務的類型拆分成幾個子指令碼,然後引入到主指令碼中

apply from:"../resource/config.gradle"
全域變數定義及引用

可以在頂層build.gradle指令碼中定義一些全域變數,提供給子指令碼引用

ext {    // global variables definition    compileSdkVersion = ‘Google Inc.:Google APIs:23‘    buildToolsVersion = "23.0.2"    minSdkVersion = 14    targetSdkVersion = 23}

子指令碼引用

android {    compileSdkVersion rootProject.ext.compileSdkVersion    buildToolsVersion rootProject.ext.buildToolsVersion    defaultConfig {        minSdkVersion rootProject.ext.minSdkVersion        targetSdkVersion rootProject.ext.targetSdkVersion    }}
構建參數篇

構建參數設定

AndroidManifest預留位置,BuildConfig以及資源配置

根據版本類型和構建變種定義不同的變數值供程式引用

manifestPlaceholders = [APP_KEY:"release"]buildConfigField "String", "EMAIL", "\"[email protected]\""resValue "string", "content_main", "Hello world from release!"

buildConfigField支援Java中基礎資料型別 (Elementary Data Type),如果是字串,記得加轉義後的雙引號 resValue支援res/values下的資源定義,字串無需叫轉義後的雙引號

設定支援的語言

利用這個配置可以去掉三方庫中無用的語言

android {    defaultConfig {        resConfigs "zh"    }}
重新命名產出的檔案

需要將產出的aar/apk移到另外一個地方的時候

android.libraryVariants.all { variant ->    variant.outputs.each { output ->        if (output.outputFile != null && output.outputFile.name.endsWith(‘.aar‘)) {            def name = "${rootDir}/demo/libs/library.aar"            println name            output.outputFile = file(name)        }    }}
刪除unaligned apk

刪除無用的apk中間產物

android.libraryVariants.all { variant ->    variant.outputs.each { output ->        if (output.zipAlign != null) {            output.zipAlign.doLast {                output.zipAlign.inputFile.delete()            }        }    }}
將自訂的任務加入構建流程

有時候編寫了一些自訂的任務,希望加入到構建流程中對輸入做預先處理或者對輸出做後處理

project.tasks.whenTaskAdded { task ->    android.applicationVariants.all { variant ->        if (task.name == "prepare${variant.name.capitalize()}Dependencies") {            task.dependsOn ":library:assemble${variant.name.capitalize()}"        }    }}

比如這裡app工程依賴library的構建,可以這樣手工指定依賴關係

打包選項

有時候引用的三方庫會帶有一些設定檔xxxx.properties,或者license資訊,打包的時候想去掉這些資訊,就可以這樣做

android {    packagingOptions {        exclude ‘proguard-project.txt‘        exclude ‘project.properties‘        exclude ‘META-INF/LICENSE.txt‘        exclude ‘META-INF/LICENSE‘        exclude ‘META-INF/NOTICE.txt‘        exclude ‘META-INF/NOTICE‘        exclude ‘META-INF/DEPENDENCIES.txt‘        exclude ‘META-INF/DEPENDENCIES‘    }}
lint選項開關

lint會按預設選項會做嚴格檢查,遇到包錯誤會終止構建過程,可以用如下開關關掉這個選項,不過最好是重視下lint的輸出,有問題及時修複,避免潛在問題

android {    lintOptions {        abortOnError false    }}
依賴庫篇

三方庫(本地,maven)的依賴和工程庫依賴關係

依賴庫按構建目標定製

不同的依賴庫可以按構建目標做定製,比如freemium的變種整合了廣告,就可以這樣寫

dependencies {    freemiumCompile ‘com.google.android.gms:ads:7.8.0‘}
aar本地庫依賴

jar本地庫的依賴很容易寫,arr本地庫的依賴稍微麻煩些

allprojects {   repositories {      jcenter()      flatDir {        dirs ‘libs‘      }   }}dependencies {    compile(name:‘本地庫aar的名字,不用加尾碼‘, ext:‘aar‘)}
NDK篇

NDK配置

只保留某一個abi,比如arm-eabi

為了包大小的考慮,去掉多餘的本地庫

android { defaultConfig {        ndk {            abiFilters ‘armeabi‘        }    }}
總結篇

只有更好,木有最好;
只有總結,木有完結;
筆者從軟體時代開始使用構建工具和系統,一路從mk, make, cmake, qmake, 再到ant,maven, ivy, 到如今互連網時代的gradle,sbt,構建配置越來越抽水馬桶化的人性體驗和更多的功能,讓開發人員更專註於自己的業務開發,每個人都在自己的崗位專註的精耕細作專業的事,這樣才會有更高效的產出和成果,用好手頭的每一個工具,掌握各種姿勢和適用情境,這會是高效率高品質開發的良好的開端。
希望大家的姿勢都用對了,妥妥的,新姿勢,新情境,也請反饋給我,謝謝!

感謝篇

加速GRADLE構建的6個技巧
安卓新的構建系統
GRADLE官網

 

轉:GRADLE構建最佳實務

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.