Gradle模組化配置:讓你的gradle程式碼控制在100行以內

wustor發表於2017-12-07

概述

我們知道,Android Studio是利用gradle進行構建的,我們經常接觸到的gradle指令碼是build.gradle,build.gradle有兩個,一個在project下,一個是在app目錄下,隨著專案的迭代,我們會在app目錄下的gradle中新增很多依賴,project下的gradle卻不會發生很大的變化,所以會導致app下面的gradle檔案越來越大,有時候查詢對應的方法以及task非常不方便,尤其是在整合了tinker熱修復之後,app下面的gradle已經達到了將近1000多行,最近剛好有時間認真研究了一下gradle,確切地說是groovy,然後通過指令碼依賴實現了gradle解耦,成功的把app目錄下的gradle程式碼控制在100行以內。

正文

常見配置

通常的做法是在project目錄下新建一個config.gradle檔案,如下:

ext {
    android = [
            compileSdkVersion: 25,
            buildToolsVersion: "25.0.3
    ]
    supportLibrary = "25.4.0"
    tinkerVerison = "1.9.1"
    dependencies = [
  "multidex"       : "com.android.support:multidex:1.0.1",
  "okhttp3"       : "com.squareup.okhttp3:okhttp:3.9.0"
}

複製程式碼

然後在project下的build.gradle檔案中引用

apply from: "config.gradle"
複製程式碼

再接著在app目錄下的build.gradle中獲取並使用

apply plugin: 'com.android.application'
apply from: "package.gradle"

def cfg = rootProject.ext.android
def librarys = rootProject.ext.dependencies

android {
    compileSdkVersion cfg.compileSdkVersion
    buildToolsVersion cfg.buildToolsVersion
    dexOptions {
        jumboMode = true
    }
//此處省略一萬行程式碼
}
dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
	compile librarys["multidex"]
	compile librarys["okhttp3"]
//此處省略一萬行程式碼
}
複製程式碼

這種方式能夠將我們的gradle統一進行管理,雖然並不能減少app的目錄下的build.gradle的程式碼量之前,但是覺得夠用了,本身對基於groovy的gradle不是很熟,雖然隨著專案迭代,app目錄下的build.gradle程式碼量越來越大,尤其是當專案整合了tinker之後,而後整合了packer-ng-plugin打包,以及加入了一些自定義的Task之後,程式碼會顯得非常臃腫,有時候改一個東西,需要找很久,由於對groovy不是很熟,在網上也看過一些文章,基本上都是在介紹gradle的基本知識以及依賴統一管理,加上在gradle裡面寫程式碼沒有提示,一度讓我以為這可能就是build.gradle的最終版了,直到最近專案剛上線,稍微有點空閒,然後決定徹底簡化一下gradle程式碼。

url優化

按照config的配置,一般來講,我們開發的時候至少會有兩個伺服器地址,正式跟測試,為了統一管理,先是寫死在buildType裡面的

 buildTypes {
        release {
            minifyEnabled true
            shrinkResources true
            buildConfigField "String", "AlphaUrl", "\"releaseUrl1\""
//          buildConfigField "String", "AlphaUrl", "\"releaseUrl2\""
        }
        debug {
            minifyEnabled true
              buildConfigField "String", "AlphaUrl", "\"debugUrl1\""
//            buildConfigField "String", "AlphaUrl", "\"debugUrl2/\""

        }
    }

複製程式碼

當我們的伺服器地址只有一個正式的或者一個測試的時候,這樣寫完全OK的,但是如果是有多個地址的話,你每切換一次,都需要重新同步一下,還有就是,多人協作開發的時候,每次從Git伺服器上面更新程式碼,只要更新到app目錄下的gradle,都是需要重新同步的,參照config的配置,我們如果是引用的話就每次只需要讀取引用的那個url,切換隻需要修改config中的程式碼就可以了,下面是進行的優化: 在config中新增程式碼

    url = [
  "debug"  : "debugUrl1",
//"debug"  : "debugUrl2",
//"debug"  : "debugUrl3",
   "release": "releaseUrl1",
// "release": "releaseUrl2"

    ]
複製程式碼

重新引用

//獲取
def url = rootProject.ext.url
//使用
//debug
buildConfigField "String", "AlphaUrl", "\"${url["debug"]}\""
//release
buildConfigField "String", "AlphaUrl", "\"${url["release"]}\""

複製程式碼

依賴優化

先看一看之前的程式碼

dependencies {
 compile fileTree(include: ['*.jar'], dir: 'libs')
 compile librarys["multidex"]
 compile librarys["supportAppcompat"]
 //此處省略一萬行程式碼

複製程式碼

實際上library就是一個Map,其實這句程式碼轉化成Java就是

 compile fileTree(include: ['*.jar'], dir: 'libs')
 HashMap<String,String> hashMap=new HashMap<>();
 compile hashMap.get("multidex")
 compile hashMap.get("supportAppcompat")
複製程式碼

其實可能你也知道了,實際上我們完全可以寫一個迴圈來簡化這些程式碼,就跟HashMap的遍歷一樣

    dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    librarys.each { k, v -> compile v }
}
複製程式碼

模組化配置

在我們進行常規的gradle配置中,我們並沒有在project下的build目錄新增很多程式碼,只是新建了一個config.gradle檔案,然後再project目錄下新增了一行依賴,所以就能夠呼叫config中的程式碼,同樣的,我們也可以把tinker,packer-ng-plugin,以及自定義task的配置檔案用一個gradle檔案進行配置,然後在app的目錄下進行引用,實際上就是利用了gradle的外掛依賴。

tinker配置

首先在app的目錄下新建一個tinker.grale配置檔案,為什麼是tinker目錄下,因為tinker的執行需要依賴'com.android.application'這個外掛,所以必須放在這個目錄下,然後複製貼上tinker的配置程式碼,注意不要忘記修改tinker的id,我是在project目錄下統一進行配置的tinker,所以只需要呼叫config中的程式碼

//此處省略一萬行程式碼

def getTinkerIdValue() {
    return rootProject.ext.tinker.id

}
//此處省略一萬行程式碼

複製程式碼

然後在app下面的build.gradle中進行引用,注意看一下這行程式碼的位置,不要放在最開始,因為在依賴tinker.gradle檔案的時候,不然你是打不了tinker的補丁的,tinker需要讀取application的一些資訊,放在buildTypes 之後,當時我也是除錯了好久。

apply plugin: 'com.android.application'
def cfg = rootProject.ext.android
def librarys = rootProject.ext.Dependencies
def tinker = rootProject.ext.tinker
def url = rootProject.ext.url

 buildTypes {
  //此處省略一萬行程式碼
}
apply from: "tinker.gradle"

複製程式碼

到此,tinker就可以使用了,下面繼續配置packer的gradle

packer配置

新建package.gradle檔案

apply plugin: 'packer'
packer {
    archiveNameFormat = '${buildType}-v${versionName}-${channel}'
    archiveOutput = new File(project.rootProject.buildDir, "apks")
    channelList = ['xiaomi','meizu']
}
}
複製程式碼

在app下的build.gradle中新增依賴

apply plugin: 'com.android.application'
apply from: "package.gradle"
複製程式碼
task配置

雖然Android Studio的application自帶了很多task,但是並不能滿足我們有些需求,比如我需要用Python將測試包上傳至fir,就需要自定義task,所以我也打算把這部分給分離出來,新建upload.gradle

ext {
   //此處省略一萬行程式碼
    startUpload = this.&startUpload
}
//上傳至fir
def startUpload() {
 //此處省略一萬行程式碼
}

複製程式碼

在project中引用

apply from: "upload.gradle"
複製程式碼

在app的build.gradle中可以直接呼叫

task toFir << {
    startUpload()
}
複製程式碼

toFir <<,其實是gradle的語法,如果不加<<的話,每次編譯的時候都會執行這個task,加了<<,只有執行這個task的時候才會執行裡面的程式碼

gradle程式碼.png

只有81行,這樣一來,除錯就很輕鬆了,哪個指令碼除了問題,就直接去除錯相應的指令碼就好了,不用在自己的gradle裡面改來改去。

執行測試

tinker 測試

執行命令gradlew tinkerpatchDebug或者開啟右側的視覺化工具欄點選tinker下的tinkerpatchDebug,執行測試,執行結果:

Result: final signed patch result: G:\Note\ChuangMei\app\build\outputs\tinkerPatch\debug\patch_signed.apk, size=2110
Result: final signed with 7zip patch result: G:\Note\ChuangMei\app\build\outputs\tinkerPatch\debug\patch_signed_7zip.apk, size=2439
Warning: patch_signed_7zip.apk is bigger than patch_signed.apk 329 byte, you should choose patch_signed.apk at these time!
Tinker patch done, total time cost: 8.806000s
Tinker patch done, you can go to file to find the output G:\Note\ChuangMei\app\build\outputs/tinkerPatch/debug
-----------------------Tinker patch end-------------------------
BUILD SUCCESSFUL in 3m 16s

複製程式碼
packer測試

執行命令gradlew clean apkDebug執行測試,執行結果:

> Task :app:apkDebug
============================================================
PackerNg - https://github.com/mcxiaoke/packer-ng-plugin
============================================================
Variant: debug
Input: G:\Note\ChuangMei\app\build\outputs\apk\app-debug.apk
Output: G:\Note\ChuangMei\build\apks
Channels: [xiaomi meizu]
Generating: debug-v1.6.3-xiaomi.apk
Generating: debug-v1.6.3-meizu.apk
Outputs: G:\Note\ChuangMei\build\apks

複製程式碼
task測試

執行命令 gradlew task toFir,執行結果:

開始上傳至fir
http://api.fir.im/apps
success_apk:{"is_completed":true}
success_icon:{"is_completed":true}
上傳結束 with value 0
複製程式碼

幾點說明

gradle所放位置

為什麼有的是放在project目錄下,有的是放在app的目錄下,因為gradle引用預設的是當前路徑,這樣放我引用的時候就不需要去配置所引用的gradle路徑,當然如果你原因放置在同一個目錄下面也是OK的,只需要在apply的時候加上引用的path即可。

引用位置

為什麼有的引用是放在頭部,有的引用需要放置在中間,這個取決於引用的外掛是否需要讀取application的配置資訊,如果是tinker,必須放置在中間,因為它生成patch包需要獲取很多application資訊,如果是packer打包的話,則不需要,這個需要格外留意一下,不然會有很多莫名其妙的錯誤。

方法呼叫

在同一個gradle指令碼里面,方法呼叫是很簡單的,但是當我們有多個gradle指令碼的時候,如何相互呼叫彼此的方法呢,其實我之前想優化的時候,也是卡在這裡,因為屬性呼叫很簡單,gradle提供了ext,所以我們可以很容易的獲取其他gradle的屬性,如果我們現在有兩個gradle,一個是first.gradle,一個是second.gradle,我想在second.gradle裡面呼叫first.gradle中的方法,應該怎麼做呢? 只需要在first.gradle中進行如下配置

ext{ 
    test= this.&test
 } 
	def  test(){  
       println("我被呼叫了")
}  
複製程式碼

然後在second.gradle中進行配置

//直接呼叫
   test()
//通過task呼叫
task CustomTask << {
    test()
}
複製程式碼

#####關於程式碼

package 需要配置簽名,在keystore.properties中進行配置,可以進行多渠道打包 tinker 為了保證對gradle進行模組化分離不影響專案的構建,所以我都是用自己的真實專案進行構建的,因為簡單的Demo很難模擬真實專案中的構建環境,所以demo中我只提供了gradle的配置檔案,沒有進行tinker的配置,但是已經對tinker進行了模組化分離。 upload 上傳至fir的程式碼是利用Python指令碼進行上傳的,需要配置Python環境以及安裝requests庫,感興趣的話可以檢視一下我之前的文章Python(一)Android藉助Python實現自動打包上傳fir

程式碼下載

相關文章