本篇來自 wustor 的投稿,分享了一個Gradle模塊化配置的實現技巧,但願你們會喜歡!android
wustor 的博客地址:git
https://www.jianshu.com/u/94dc45995a85github
概述api
咱們知道,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行之內。服務器
正文app
一般的作法是在project目錄下新建一個config.gradle文件,以下:ide
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就可使用了,下面繼續配置walle的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的時候纔會執行裏面的代碼
只有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中進行配置
//直接調用 startUpload() //經過task調用 task toFir << { startUpload() }
結語
package
須要配置簽名,在keystore.properties中進行配置,能夠進行多渠道打包
tinker
爲了保證對gradle進行模塊化分離不影響項目的構建,因此我都是用本身的真實項目進行構建的,由於簡單的Demo很難模擬真實項目中的構建環境,因此demo中我只提供了gradle的配置文件,沒有進行tinker的配置,可是已經對tinker進行了模塊化分離。
upload
上傳至fir的代碼是利用Python腳本進行上傳的,須要配置Python環境以及安裝requests庫。代碼 地址:
https://github.com/wustor/GradleModule
若是有什麼問題歡迎留言哈,謝謝!