Android工程gradle詳解

mymdeep發表於2019-03-01

版本的統一管理

當我們的工程中有許多module的時候,分開管理編譯版本,minsdk將會是一件很麻煩的事,因為一個library的改動,可能會影響到其他module。這時我們就需要對所有的版本進行統一的管理,管理的方式有兩種:

rootProject

我們可以把一些需要用的欄位都放在project的build.gradle(注意是project的不是module的)中:

ext {   
 compileSdk = 21    
minSdk = 11    
targetSdk = 23    
support = "23.1.1"   
buildTools = "21.0.1"   
 buildstyle ="debug"
}複製程式碼

這樣,在module的build.gradle中可以進行讀取:

defaultConfig {   
 applicationId "android.com.testgradle"    
minSdkVersion rootProject.ext.minSdk    
targetSdkVersion rootProject.ext.targetSdk    
versionCode 1   
 versionName "1.0"
}複製程式碼

gradle.properties

找到工程目錄下的gradle.properties檔案,如果沒有也可以自己建立:

ANDROID_COMPLILE_SDK_VERSION=21
ANDROID_BUILD_SDK_VERSION=21.0.1
ANDROID_TEXT=test複製程式碼

然後在各個module的build.gradle中可以引用:

compileSdkVersion ANDROID_COMPLILE_SDK_VERSION as int
buildToolsVersion ANDROID_BUILD_SDK_VERSION複製程式碼

需要注意的是在gradle.properties中宣告的格式都是string型別,如果如要轉化成int型別,可以用as int 進行強制轉化。

#程式中對buildTypes的區分

##buildTypes是對不同build型別的處理
當你點選執行按鈕的時候會根據build Variant進行對應的方式編譯。
build Variant可以在這裡進行選擇:

Android工程gradle詳解
Paste_Image.png

library的buildTypes

預設情況下被依賴工程會使用release模式,與上層依賴的app工程選擇的模式無關
需要在build.gradle中進行設定:

defaultPublishConfig "debug"複製程式碼

新增buildTypes

你可以在buildTypes,根據需要新增一個型別,如下程式碼所示:

buildTypes {    
debug {       
 buildConfigField("String","TEXT","\"這個字串來自debug模式\"")        
minifyEnabled false        
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'    
}    
stag {        
buildConfigField("String","TEXT","\"這個字串來自test模式\"")        
minifyEnabled false        
signingConfig signingConfigs.debug       
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'   
 }   
 release {       
buildConfigField("String","TEXT","\"這個字串來自release模式\"")        
minifyEnabled false        
signingConfig signingConfigs.debug        
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'    
}
}複製程式碼

不同模式字元不同

根據上面的程式碼,在不同的buildTypes中,TEXT這個變數是不一樣的,而且根據程式碼可以看出,這個變數是一個String型別,那麼我們在程式檔案中可以這樣通過BuildConfig引用:

t2.setText(BuildConfig.TEXT);複製程式碼

Variant

gradle外掛允許最終生成的包以多個維度進行組合
例如我們可以設定一下幾個維度:

productFlavors {    
red {       
 applicationId 'android.com.red'    
 versionCode 1
minSdkVersion 21
targetSdkVersion 22
}    
blue {        
applicationId 'android.com.blue'    
}    
yellow {       
 applicationId 'android.com.yellow'   
 }    
}複製程式碼

每個維度中可以設定這個版本的最小sdk限制,以及targetsdkversion

假設我們設定的維度有red blue yellow但是結合之前講過的buildType(假設只有debug和release)
那麼將會出現以下構建:
blueDebug和blueRelease
yellowDebug和yellowRelease
redDebug和redRelease
Gradle會為每一個Variant建立一個任務
對應如下:
gradle assembleBlue 會生成debug和release兩個版本
gradle assembleDebug 會生成blue yellow red三個版本
gradle assembleBlueDebug 會生成bluedebug一個版本
這裡還有一個用處需要提一下:
Gradle在打包android應用之前會將所有的程式碼,資原始檔,包括manifest進行結合,當然library也會提供額外的資源,這些也會進行合併。

字串讀取

如果我們在建立一些設定時,需要動態的去更改內容,可以設定一個變數,然後從本地檔案或者打包的命令列讀取,我們這裡就拿上面提到過的BuildConfig做例子,希望程式中引用的字串是從本地讀取的或從命令列讀取的

從本地檔案中讀取

首先需要在最開始的地方設定一個變數aaa:

Android工程gradle詳解
Paste_Image.png

然後在buildTypes的debug模式中修改對應的程式碼:

debug {    
buildConfigField("String","TEXT","\""+aaa+"\"")    
minifyEnabled false    
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}複製程式碼

然後在工程目錄下建立一個本地檔案test.properties,並新增內容:

Android工程gradle詳解
Paste_Image.png

然後回到你剛才定義字串的build.gradle中,新增:

if(rootProject.file('test.properties').exists()){ 
 java.util.Properties properties = new Properties()   
properties.load(rootProject.file('test.properties').newDataInputStream())   
aaa = properties.getProperty('debug.text')    
println("!!!!!"+aaa)
}else {
    aaa = "檔案沒找到"
}複製程式碼

即可讀取到你本地檔案的字串

從命令列讀取

如果是從命令列讀入就更加簡單,只需要將上面讀取檔案的程式碼改為:

aaa = new String(System.console().readLine("請輸入字串:"))複製程式碼

編譯命令

首先想看一個工程包含了多少task,需要切到這個工程目錄下:

gradle tasks複製程式碼

會列出所有的tasks:

Android工程gradle詳解
Paste_Image.png

當然篇幅有限,這裡不貼出所有的任務了,我們只需要知道編譯命令即可,如圖可以看到,如果想編譯debug版本,使用:

assembleDebug複製程式碼

其它同理即可。
如果使用build則回編譯出所有的版本

衝突問題

Android工程gradle詳解
Paste_Image.png

在執行打包的時候很有可能會出現如上問題,這是由於依賴的jar衝突問題,我們可以分析一下工程結構。
我現在的結構是:

Android工程gradle詳解
Paste_Image.png

現在調整一下結構:

Android工程gradle詳解
Paste_Image.png

把jar以module的形式提供就不會出現衝突。

編譯流程

Android工程gradle詳解
Paste_Image.png

Android工程gradle詳解
Paste_Image.png

Task任務

執行順序

例如我們新建一個任務:

task umengtest{    
println("aaaaaaaaa")    
println("bbbbbb")
}複製程式碼

他的執行如下:

Android工程gradle詳解
Paste_Image.png

它並不是在執行任務的時候執行的,而是在執行任務之前就列印了。
這是由於gradle構建有三個階段:
初始化階段,配置階段,執行階段。
上面的例子實際是列印在了配置階段,換句話說,你不執行這個任務,執行這個工程別的任務也會列印。
例如我再寫一個任務:

task umengtestaa<<{    
println("ccc")    
println("dddd")
}複製程式碼

這次執行這個任務:

Android工程gradle詳解
Paste_Image.png

你會發現也列印aaaa這說明這不是在真正的執行階段執行的,為了保證任務的可控性,可以像上面umengtestaa那樣寫加一個<<符號
這樣就可以保證了任務的可控性:

Android工程gradle詳解
Paste_Image.png

或者使用:

task umengtest{    
doLast{        
println("aaaaaaaaa")        
println("bbbbbb")    
}
}複製程式碼

打包任務

有了上面的基礎,下面就可以說一下打包的任務了,根據我們之前的工程,我們有一個app的module和四個library module,我需要列印出一個apk和四個jar,明確了任務,現在可以開始實施了。
開啟工程的build.gradle

def sdk = [        
root : 'build/sdk/',
]task dabao( type:Zip) {    
dependsOn('cp_main')    
def name = 'umeng_test'  ;    
destinationDir = file('build')   
 archiveName = name + '.zip'    
from('build/sdk') {
        into( name )   
 }
}
task cp_main(type: Copy, dependsOn: ['app:assembleRelease'] ) {    
destinationDir = file( sdk.root )    
from('app/build/outputs/apk') {        
include('app-release.apk')        
rename ('app-release.apk','test.apk' )        
into('.')    
}    
from('mylibrary1/build/intermediates/bundles/release') {        
include('classes.jar')        
rename ('classes.jar','library1.jar' )        
into('.')    
}    
from('mylibrary2/build/intermediates/bundles/release') {        
include('classes.jar')        
rename ('classes.jar','library2.jar' )        
into('.')   
 }    
from('mylibrary3/build/intermediates/bundles/release') {        
include('classes.jar')        
rename ('classes.jar','library3.jar' )        
into('.')    
}    
from('mylibrary4/build/intermediates/bundles/release') {        
include('classes.jar')       
 rename ('classes.jar','library4.jar' )       
 into('.')    }
}複製程式碼

這時,我們再去找一下工程的build資料夾下可以發現:

Android工程gradle詳解
Paste_Image.png

打包不同內容的module

打包不同內容的module可以利用之前講過的variant或這個buildtypes來控制,這裡就不說了,不明白的,可以回頭再去看一下結合上面的打包指令碼沒有什麼難度,然而還有一種需求,不是某個變數或者包名的更改,而是兩個版本中兩個檔案的不同,我們可以試一下修改variant的方式來實現,在app的build.gradle中:

 productFlavors {

        pay {



        }
        free {

        }

    }複製程式碼

修改結構目錄:

Android工程gradle詳解
Paste_Image.png

這樣編譯出來就會有兩個不同型別的apk了

防止打包錯誤

在這裡已經說完了所有與打包相關的東西了,在最後仍然加這一個標題是交給大家如何防止專案開發者打包出錯,或者上傳包出錯後定位問題。
方法就是在打包的時候,生成一個記錄檔案,在這個包中記錄打包時間,和當前git的版本號:

def releaseTime() {    
return new Date().format("yyyyMMddHHmmss", TimeZone.getTimeZone("GMT+8"))
}
def getGitVersion() {    
return 'git rev-parse --short HEAD'.execute().text.trim()
}
task writefile( ){
    File configFile = new File('config.xml');    
if (!configFile.exists()){        
configFile.createNewFile()   
 }  
  FileOutputStream out =new FileOutputStream(configFile)   
 def result ="編譯時間:"+"${releaseTime()}\n"+"commitid:"+"${getGitVersion()}\n"    out.write(result.getBytes())    
out.close();
}
task cp_config(type: Copy, dependsOn: ['writefile'] ) {   
 destinationDir = file( sdk.root )    
duplicatesStrategy = 'exclude'   
 includeEmptyDirs = false    
from('.') { 
       include('config.xml')     
   into('.')   
 }
}複製程式碼

以上工程中所用到的所有程式碼,已經上傳github
地址如下:
github.com/mymdeep/And…
如有對groovy語法不清楚的看官,請看一下我的上一篇文章,groovy基礎知識:
www.jianshu.com/p/b58b254d8…

*更多的開發知識,可以關注我的公眾號:

Android工程gradle詳解
Paste_Image.png

相關文章