Gradle腳本指南
- 便于復用代碼和資源
- 便于創建應用中的多種變量, 用于多渠道分發apk
- 便于配置, 擴展和自定義構建過程
- 良好的IDE整合性
為什么選用Gradle?
Gradle是一種高級構建系統, 同時也是一個允許通過插件創建自定義構建邏輯的構建工具. 以下是選擇的原因:
- 基于Groovy的Domain Specifc Language(DSL), 用于描述和操作構建邏輯
- 構建文件是基于Groovy的, 并允許通過DSL混合聲明元素, 并使用代碼操作DSL元素來提供自定義邏輯.
- 通過Maven或Ivy內置依賴管理
- 非常彈性. 允許使用最佳實踐但不強制
- 插件提供了DSL和API, 可供構建文件使用
- 允許IDE整合和工具API
要求
- Gradle 2.2
- 帶有Build Tools 19.0.0的SDK. 某些功能要求最新版本
基本項目配置
Gradle使用項目根目錄下的 build.gradle 文件描述構建過程. (參見 Gradle User Guide )
簡單的構建文件
最簡單的Android項目有以下 build.gradle :
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.3.1'
}
}
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.1.0"
}
</code></pre>
其中有3個主要部分:
buildscript {...} 配置了驅動構建的代碼. 在本例中, 它內部生命了所使用的jCenter倉庫, 和一個Maven artifact依賴的classpath. 該artifact是包含用于Android的1.3.1版本的Gradle插件的庫. 注意: 這只會影響執行構建的代碼, 而不會影響項目. 該項目需要聲明自身的倉庫和依賴. 后面會講到.
然后, 應用了 com.android.application . 這是構建Android應用的插件.
最后, android {...} 配置了android構建的所有參數. 這是Android DSL的入口. 默認情況下, 只需要編譯目標和構建工具的版本. 這通過 compileSdkVersion 和 buildtoolsVersion 屬性來完成. 編譯目標與 project.properties 中的 target 屬性一致. 該屬性可以賦值為int(api級別)或與 target 相同的字符串.
重要:你應該只使用 com.android.application 插件. 使用 java 插件會導致構建錯誤.
注意:你還需要一個 local.properties 文件來設置SDK的路徑, 使用 sdk.dir 屬性.
或者你可以設置一個環境變量, 命名為 ANDROID_HOME .
以上兩種方法沒有本質區別, 你可以使用任意一種. local.properties 示例文件:
sdk.dir=/path/to/Android/Sdk
項目結構
上面的基本構建文件用于默認的目錄結構. Gradle遵循約定由于配置理念, 在可能的情況下會提供默認選項值. 基本項目會以兩個叫做”source sets”的組件開始, 一個用于主要的源代碼, 另一個用于測試代碼. 目錄如下:
- src/main/
- src/androidTest/
每個目錄內部還包含子目錄. 對于java和Android插件, Java源代碼和資源在如下目錄:
- java/
- resources/
對于Android插件, 會有如下額外的文件:
- AndroidManifest.xml
- res/
- assets/
- aidl/
- rs/
- jni/
- jniLibs/
*.java 文件都位于 src/main/java , 手冊文件位于 src/main/AndroidManifest.xml
注意: src/androidTest/AndroidManifest.xml 會自動生成
配置項目結構
當默認項目結構不足以使用時, 可以對其進行配置.
Android插件使用類似的語法, 但由于它使用了自己的 sourceSets , 因此需要在 android 代碼塊中配置. 以下是示例, 使用老的項目結構(在Eclipse中的)保存主要代碼, 然后將 androidTest 的 sourceSet 重新映射到 tests 目錄:
android {
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
}
androidTest.setRoot('tests')
}
}
</code></pre>
注意:由于老的項目結構將所有的源代碼文件(Java, AIDL和RenderScript)都放在相同的目錄, 我們需要將 sourceSet 中的所有新元素重新映射到相同的 src 目錄下.
注意: setRoot() 將整個 sourceSet (及其子目錄)移動到了一個新的目錄. 即將 src/androidTest/* 移動到了 test/* . 這是Android的標準, 不能用于Java sourceSets.
構建任務
通用任務
在構建文件中配置一個插件可以自動生成一系列的構建任務. Java插件和Android插件都可以. 任務的約定如下:
- assemble
組裝項目的輸出
- check
運行所有檢查
- build
同時執行 assemble 和 check
- clean
清空項目的輸出
assemble , check 和 build 實際不做任何事. 他們只是插件的”錨點”任務, 并添加了獨立的任務來執行實際任務.
這允許你總是調用相同的任務, 不論項目是何種類型或使用何種插件. 例如, 使用 findbugs 插件會創建一個新的任務來運行 check , 每次執行 check 任務時都會調用.
對于命令行來說, 你可以運行如下命令來執行高級別任務:
gradle tasks
如果想看完整任務列表和依賴, 可以運行如下命令:
gradle tasks --all
注意:Gradle會自動監控任務聲明的輸入和輸出
在項目沒有任何變化的情況下, 運行兩次 build 任務時, Gradle會報告所有任務都是 UP-TO-DATE 的, 意味著不需要執行任何任務.
Java項目任務
以下是 java 插件兩個最重要的任務:
- assemble
- jar
該任務創建輸出
</li>
- check
- test
該任務運行測試
</ul> </li>
</ul>
jar 任務本身直接或間接的依賴于其他任務: 例如, classes 會編譯java代碼. 該測試使用 testClasses 進行編譯, 但該命令沒有什么用處, 因為 test 依賴于它(和 classes )
總的來說, 你可能只需要調用 assemble 或 check , 并忽略其他任務. 你可以在 這里 看到完整的Java插件任務描述
Android任務
Android插件使用相同的約定來保持同其他插件的兼容, 并增加了額外的錨點任務:
- assemble
該任務組裝項目的輸出
- check
該任務運行所有的檢查
- connectedCheck
在連接設備或模擬器的情況下運行檢查. 會同時在所有已連接的設備上執行
- deviceCheck
使用API連接遠程設備運行檢查. 用于CI服務器
- build
該任務會執行 assemble 和 check
- clean
該任務會清空項目的輸出
為了在不需要連接設備的情況下執行常規檢查, 新的錨點任務是必要的. 注意 build 不依賴于 deviceCheck 或 connectedCheck
一個Android項目至少有兩種輸出: 一個debug APK和一個release APK. 每種輸出都有自己的錨點任務來分別執行構建:
- assemble
- assembleDebug
- assembleRelease
</li>
</ul>
他們都依賴于其他任務. assemble 任務同時依賴于這兩個任務, 因此運行該指令會構建兩種APK.
提示:Gradle支持駝峰式的任務名稱簡寫, 例如:
gradle aR
與下面的命令相同
gradle assembleRelease
只要沒有其他任務匹配’aR’即可
check錨點任務有自己的依賴:
- check
- lint
</li>
- connectedCheck
- connectedAndroidTest
</ul> </li>
- deviceCheck
- 依賴于其他實現測試擴展點的插件
</ul> </li>
</ul>
最后, 插件會創建任務來安裝和卸載所有構建類型(debug, release, test), 例如
- installDebug
- installRelease
- uninstallAll
- uninstallDebug
- uninstallRelease
- uninstallDebugAndroidTest
</li>
</ul>
基本構建自定義
Android插件提供了一個寬泛的DSL來在構建系統中直接自定義大部分的內容.
Manifest入口
通過DSL可以配置最重要的Manifest入口, 例如:
- minSdkVersion
- targetSdkVersion
- versionCode
- versionName
- applicationId
- testApplicationId (用于測試APK)
- testInstrumentationRunner
示例:
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
defaultConfig {
versionCode 12
versionName "2.0"
minSdkVersion 16
targetSdkVersion 23
}
}
</code></pre>
在構建文件中使用這些manifest屬性的意義在于, 這些值可以動態設置. 例如, 你可以使用自定義邏輯從一個文件中讀取版本名稱:
def computeVersionName(){
...
}
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
defaultConfig {
versionCode 12
versionName computeVersionName()
minSdkVersion 16
targetSdkVersion 23
}
}
</code></pre>
注意:不要使用與作用域中已存在的getter方法沖突的方法名. 例如 defaultConfig {...} 會調用 getVersionName() 并自動使用 defaultConfig.getVersionName() 來替換自定義的方法.
Build Type
默認情況下, Android插件會自動為項目配置debug和release版本. 兩者的區別主要在于是否可以在安全的(非工程機)設備上調試應用, 以及APK的簽名細節. debug版本使用自動生成的已知的名稱/密碼(避免在構建過程中輸入密碼)來進行簽名. release版本在構建過程中不進行簽名, 簽名需要放在構建之后.
配置通過 BuildType 對象來完成. 默認情況下, 會創建兩個實例, 一個 debug 一個 release . Android插件允許自定義這兩個實例, 或者創建其他的 Build Type . 可以通過以下的 buildTypes DSL容器實現:
android {
buildTypes {
debug {
applicationIdSuffix ".debug"
}
jnidebug {
initWith(buildTypes.debug)
applicationIdSuffix ".jnidebug"
jniDebuggable true
}
}
}
</code></pre>
上面的代碼實現了如下功能:
- 配置了默認的 debug 構建類型:
- 設置包名為 <app applicationId>.debug 以便在同一個設備上同時安裝debug和release的apk
</li>
- 創建了一個新的構建類型 jnidebug 并使用了 debug 構建模式
- 繼續配置 jnidebug , 啟用了JNI組件的debug, 并添加了一個不同的包名前綴.
</ul>
創建一個新的 BuildType 和在 buildTypes 容器中創建一個新元素一樣簡單. 可以調用 initWith() 或用括號來配置. 參見 DSL參考手冊 來了解可用于配置構建類型的所有屬性.
如果需要修改構建屬性, BuildType 可以添加某些代碼或資源. 對于每種構建類型, 都會創建一個與之匹配的 sourceSet , 默認路徑在 src/<buildtypename>/ , 例如 src/debug/java 目錄只能添加debug APK所用的資源. 這意味著 BuildType 名稱不能使用 main 或 androidTest (這是插件強制設置的), 并且名稱必須唯一.
和其他資源目錄一樣, 構建類型資源目錄頁可以重新定義:
android {
sourceSets.jnidebug.setRoot('foo/jnidebug')
}
此外, 對于每種 BuildType , 都會創建一個新的 assemble<BuildTypeName> 任務, 例如 assembleDebug . 這就是之前提到的 assembleDebug 和 assembleRelease 的來源. 當 debug 和 release 構建類型已經創建的情況下, 他們的任務也會自動被創建. 根據這一規則, 上面的 build.gradle 規則會生成一個 assembleJnidebug 任務, 并且 assemble 會依賴于該任務.
提示:記住你可以輸入 gradle aJ 來運行 assembleJnidebug 任務
可能的用例:
- 某些權限只用于debug模式, 在release模式沒有
- 自定義實現調試
- debug模式使用不同的資源 (例如某些資源的值與簽名綁定)
BuildType 的代碼/資源可以通過如下方式使用:
- 將一個manifest合并進app的manifest
- 作為其他資源目錄的代碼
- 資源會覆蓋主資源, 替換已有的值
簽名配置
對一個應用簽名有以下要求
- 一個keystore
- 一個keystore密碼
- 一個key別名
- 一個key密碼
- 存儲類型
路徑, key名稱, 密碼和存儲類型構成了一個 簽名配置 . 默認情況下, debug 配置使用debug keystore, 它使用的是已知的密碼和默認的key.
debug keystore位于 $HOME/.android/debug.keystore , 如果沒有的話會自動創建. debug 構建類型默認使用 debug 的 SigningConfig .
可以創建其他配置或自定義默認的配置. 通過 signingConfigs DSL容器來配置:
android {
signingConfigs {
debug {
storeFile file("debug.keystore")
}
myConfig {
storeFile file("other.keystore")
storePassword "android"
keyAlias "androiddebugkey"
keyPassword "android"
}
}
buildTypes {
foo {
signingConfig signingConfigs.myConfig
}
}
}
</code></pre>
上面的代碼將debug keystore的路徑修改為項目的根目錄. 這會自動影響所有使用該配置的的 Build Type , 在本例中就是 debug 構建類型. 該代碼同時會創建一個新的 Signing Config , 并且新的構建類型會使用這個配置.
注意:只有默認路徑下的debug keystore會被自動創建. 修改debug keystore路徑不會生效. 使用其他名稱在默認debug keystore路徑創建 SigningConfig 可以被自動創建. 換句話說, 是否生成與keystore的路徑有關, 而與配置的名稱無關.
注意:keystore的路徑通常相對于項目的根目錄, 但也可以使用絕對路徑, 雖然這種方式是不推薦的(除了debug的, 因為會被自動創建)
注意:如果你通過版本控制檢出這些文件, 你可能不希望文件中出現密碼. 這篇Stack Overflow文章 展示了如果從命令行, 或環境變量中讀取值.
依賴, Android庫和多項目設置
Gradle項目可以依賴于其他組件. 這些組件可以是外部二進制包, 或者其他Gradle項目.
二進制包依賴
本地包
配置依賴于外部庫的jar, 你需要在 compile 配置中添加依賴. 以下代碼在 libs 目錄下添加了對所有jar的依賴:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}
android {
...
}
</code></pre>
注意: dependencies DSL元素是標準Gradle API的一部分, 并且不屬于 android 元素之內
compile 配置用于編譯主應用. 編譯配置中的所有內容都會被加入編譯的classpath 和 最終的APK. 還有其他可能的配置可用于添加依賴:
- compile : 主應用
- androidTestCompile : 測試應用
- debugCompile : debug構建類型
- releaseCompile : release構建類型
由于不使用 Build Type 構建APK時不可能的, 所以APK通常會配置兩種或多種配置: compile 和 <buildtype>Compile . 創建一個新的 Build Type 會自動根據他的名字創建一個新的配置. 這在debug版本使用一個自定義庫(比如報告崩潰)而release版本不需要使用該庫的情況下, 或是他們依賴于不同版本的庫的情況下很有用. (參見 Gradle文檔 )
遠程artifact
Gradle支持從Maven或Ivy倉庫拉取artifact. 首先必須將倉庫添加到列表中, 然后必須按照Maven或Ivy方式聲明依賴.
repositories {
jcenter()
}
dependencies {
compile 'com.google.guava:guava:18.0'
}
android {
...
}
</code></pre>
注意: jcenter() 是指定倉庫URL的快捷方式. Gradle支持遠程和本地倉庫.
注意:Gradle會跟進所有依賴. 也就是說如果一個依賴有他自己的其他依賴, 這些依賴也會被拉取.
多項目設置
Gradle項目可以使用多項目設置同時依賴于其他Gradle項目. 多項目配置可以通過將所有項目作為制定根項目的子目錄來實現. 例如, 有如下結構:
MyProject/
- app/
- libraries/
- lib1/
- lib2/
</code></pre>
我們可以看到有3個項目. Gradle可以通過以下名稱來引用他們:
:app
:libraries:lib1
:libraries:lib2
每個項目都有有自己的 build.gradle 來聲明如何進行構建. 此外, 在項目根目錄還會有一個叫做 settings.gradle 的文件來聲明項目. 結構如下:
MyProject/
| settings.gradle
- app/
| build.gradle
- libraries/
- lib1/
| build.gradle
lib2/
| build.gradle
</code></pre>
settings.gradle的內容很簡單. 它定義了哪個目錄是一個Gradle項目:
include ':app', ':libraries:lib1', ':libraries:lib2'
:app 項目可能會依賴某些庫, 可以通過如下的聲明實現:
dependencies {
compile project(':libraries:lib1')
}
庫項目
在上面的多項目設置中, :libraries:lib1 和 :libraries:lib2 可以是Java項目, :app Android項目可以使用它們的 jar 文件. 但是, 如果你想共享Android API或使用Android風格的資源, 這些庫不能使用普通的Java項目, 必須使用Android庫項目.
創建一個庫項目
一個庫項目同普通Android項目相似, 但有一些區別. 由于構建庫與構建應用不同, 所以會使用另外一種插件. 在內部兩種插件會共享大部分相同的代碼, 他們都是由同一個 com.android.tools.build.gradle jar文件提供的.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.3.1'
}
}
apply plugin: 'com.android.library'
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
}
</code></pre>
以上代碼創建了一個庫項目, 并使用API 23來編譯. SourceSets , build types 和依賴庫的處理方式都是相同的, 因為他們在同一個應用項目, 并通過相同的方式進行自定義.
項目和庫項目的區別
庫項目的主要輸出是一個 .aar包 (即android archive縮寫). 它是編譯后的代碼(例如jar文件或.so文件)和資源(manifest, res, assets). 一個庫項目可以生成一個測試apk來單獨測試一個庫. 它會使用相同的錨點任務( assembleDebug , assembleRelease ), 因此使用的命令并沒有什么區別. 除此之外, 庫也可以表現的同應用項目一樣. 庫擁有構建類型和渠道, 并可以潛在生成多于一種版本的aar. 注意, 大部分的構建類型配置不適用于庫項目. 但是你可以使用自定義 sourceSet 來在測試和非測試的情況下動態改變庫所依賴的內容.
引用庫
引用一個庫和引用其他項目一樣:
dependencies {
compile project(':libraries:lib1')
compile project(':libraries:lib2')
}
注意:如果你有一個以上的庫, 那么庫的順序會很重要.
庫的發布
默認情況下, 一個庫只會發布他的 release 變量. 該變量可以被整個項目使用來引用這個庫. 無論他們構建的是何種變量. 這是Gradle自身限制產生的一個臨時限制, 我們正努力去除該限制. 你可以控制要發布何種變量:
android {
defaultPublishConfig "debug"
}
注意這個發布配置名稱引用了完整的變量名. release 和 debug 僅適用于沒有渠道的情況. 如果你希望在使用渠道時改變默認發布變量, 你可以這樣寫:
android {
defaultPublishConfig "flavor1Debug"
}
發布一個庫的所有變量也是可以的. 我們計劃對普通的項目對項目的依賴方式使用這種方式(例如上例), 但目前由于Gradle限制我們還無法做到(我們正在努力修復)
默認情況下, 發布所有變量是禁用的. 以下代碼可以啟用該功能:
android {
publishNonDefault true
}
發布多個變量意味著會發布多個aar文件, 而不是一個aar含有多個變量, 理解這點很重要. 每個aar包都含有一個單獨的變量. 發布一個變量意味著將這個aar作為Gradle項目的輸出. 這可以用于發布到maven倉庫, 或者用于其他項目的依賴.
Gradle有一個概念是”默認artifact”. 可以通過如下寫法實現:
dependencies {
compile project(':libraries:lib2')
}
若要創建一個依賴于另一個已發布的artifact, 你需要指定具體使用哪一個:
dependencies {
flavor1Compile project(path: ':lib1', configuration: 'flavor1Release')
flavor2Compile project(path: ':lib1', configuration: 'flavor2Release')
}
重要:注意發布配置是一個完整變量, 包含了構建類型
重要:當啟用非默認發布時, Maven發布插件會將這些額外變量作為額外包進行發布. 也就是說該方式并不能完全兼容maven倉庫的發布. 你應該發布單獨變量到倉庫, 或是對所有內部項目依賴都設置相同的配置.
測試
構建測試應用的功能已經集成到了應用項目. 目前不再需要創建單獨的測試項目.
單元測試
在1.1中加入了單元測試的支持, 本節余下內容描述了構建一個單獨測試APK并在真機(或模擬器)上使用”instumentation測試”.
基礎和配置
正如之前提到的, main 目錄下面是 androidTest 目錄, 默認位于 src/androidTest/ .
<p>@todo</p>
解決main apk和test apk的沖突
<p>@todo</p>
運行測試
如之前所說, check需要一個已連接的設備才能執行, 它通過一個叫做 connectedCheck 的錨點任務來執行. 這基于 connectedDebugAndroidTest 任務. 該任務執行以下內容:
- 保證app和測試app被構建 (基于 assebleDebug 和 assebleDebugAndroidTest )
- 安裝兩個app
- 運行測試
- 卸載兩個app
如果連接了多個設備, 所有測試會在所有已連接設備上并行運行. 如果任一個設備上的任一測試失敗, 整個構建都會失敗
測試Android庫
<p>@todo</p>
測試報告
當運行單元測試時, Gradle輸出一個HTML報告來查看結果. Android插件根據此構建并擴展了HTML報告, 使其匯總所有連接設備的結果. 所有的測試結果存儲在 build/reports/androidTests/ . 可以通過如下代碼配置輸出路徑:
android {
...
testOptions {
resultsDir = "${project.buildDir}/foo/results"
}
}
android.testOptions.resultsDir 的值通過 Project.file(String) )來獲得
多項目報告
<p>@todo</p>
Lint支持
你可以為具體的variant運行lint, 例如 ./gradlew lintRelease , 也可以為所有variant運行lint, 例如 ./gradlew lint . 單獨運行則產生單獨的報告. 你可以添加lintOptions部分來配置lint.
android {
lintOptions {
// 關閉指定問題的檢查
disable 'TypographyFractions','TypographyQuotes'
// 開啟指定問題的檢查
enable 'RtlHardcoded','RtlCompat', 'RtlEnabled'
// 只檢查指定的問題
check 'NewApi', 'InlinedApi'
}
}
</code></pre>
Build Variant
新構建系統的一個目的是為同一個應用創建不同的版本
主要有兩種用例:
- 相同應用的不同版本
例如, 一個免費/演示版本和一個”高級”付費版本
- 相同應用在Google Play商店中的多apk打包
參見 多apk
- 以上兩種情況的結合
我們的目標是可以用相同的項目生成不同的apk.
Product Flavor
product flavor定義了項目構建的自定義版本. 一個單獨項目可以有多種flavor, 它可以改變所生成的應用
這種設計的目的是用于產生最小的區別. 如果問你”這些是相同的應用嗎?”, 答案是”是”的話, 那么應該使用庫項目.
Product flavor使用 productFlavors DSL容器來聲明:
android {
....
productFlavors {
flavor1 {
...
}
flavor2 {
...
}
}
}
</code></pre>
這會創建兩種flavor, 名為 flavor1 和 flavor2 .
注意:flavor的名稱不能與已存在的 Build Type 名稱, 或是 androidTest , test 重復.
Build Type + Product Flavor = Build Variant
正如我們之前所見, 每個構建類型都會生成一個新的apk. Product Flavor也有同樣作用: 項目的輸出會是所有 Build Type 和 product flavor 的組合. 形成的組合叫做 Build Variant . 例如, 如果使用默認的 debug 和 release 構建類型, 上面的例子會生成以下Build Variant:
- Flavor1 - debug
- Flavor1 - release
- Flavor2 - debug
- Flavor2 - release
沒有flavor的項目也有Build Variant, 它會默認使用 default flavor.
Product Flavor配置
每種flavor使用單獨的括號配置:
android {
...
defaultConfig {
minSdkVersion 8
versionCode 10
}
productFlavors {
flavor1 {
applicationId "com.example.flavor1"
versionCode 20
}
flavor2 {
applicationId "com.example.flavor2"
minSdkVersion 14
}
}
}
</code></pre>
注意 android.productFlavors.* 對象和 android.defaultConfig 對象都是 ProductFlavor 類型的, 也就是說他們會共享相同的屬性.
defaultConfig 為所有flavor提供了基本配置, 每種flavor可重寫任意值. 在上面的例子中, 最終的配置結果是這樣的:
- flavor1
- applicationId : com.example.flavor1
- minSdkVersion : 8
- versionCode : 20
</li>
- flavor2
- applicationId : com.example.flavor2
- minSdkVersion : 14
- versionCode : 10
</ul> </li>
</ul>
通常情況下, Build Type配置會覆蓋其他的配置. 例如, Build Type的 applicationIdSuffix 會添加到Product Flavor的 applicationId 之后. 這適用于某種配置在Build Type和Product Flavor都適用的情況. 這種情況需要具體分析. 例如, signingConfig 是其中一個可以同時配置的屬性. 它既可以通過設置 android.buildTypes.release.signingConfig 為所有release包共享相同的 SigningConfig , 也可以通過為每一個release包配置單獨的 android.productFlavors.*.signingConfig 對象來分別使用各自的 SigningConfig .
SourceSet和Dependency
與 BuildType 相同, Product Flavor 也會通過 sourceSet 使用自己的代碼和資源. 上面的例子創建了4個 sourceSet :
- android.sourceSets.flavor1
位于 src/flavor1/
- android.sourceSets.flavor2
位于 src/flavor2
- android.sourceSets.androidTestFlavor1
位于 src/androidTestFlavor1
- android.sourceSets.androidTestFlavor2
位于 src/androidTestFlavor2
這些 sourceSet 會被構建的apk使用. 在構建一個單獨的apk時, 以下規則會被使用:
- 所有的源代碼( src/*/java )都在一起, 因為所有目錄都會生成同一個輸出
- 所有Manifest都會合并入一個單獨的Manifest中. 這使得 Product Flavor 可以擁有不同的組件和權限, 與 Build Type 相似
- 所有資源(res和assets)都使用覆蓋優先級, Build Type 會覆蓋 Product Flavor , Product Flavor 會覆蓋 main sourceSet.
- 每種 Build Variant 會根據資源生成自己的R類. Variant之間不會共享.
最后, 與 Build Type 類似, Product Flavor 可以有他們自己的依賴. 例如, 如果使用flavor來分別生成一個廣告和一個付費app, 其中一個flavor可以設置一個廣告sdk, 另一個則不需要.
dependencies {
flavor1Compile "..."
}
在這個場景下, 文件 src/flavor1/AndroidManifest.xml 可能需要加入網絡權限.
還會為每個Variant創建額外的serceset:
- android.sourceSets.flavor1Debug
位于 src/flavor1Debug/
- android.sourceSets.flavor1Release
位于 src/flavor1Release/
- android.sourceSets.flavor2Debug
位于 src/flavor2Debug/
- android.sourceSets.flavor2Release
位于 src/flavor2Release/
以上這些比build type的sourceSet擁有更高的優先級, 并允許在variant級別進行自定義
構建任務
每個 Build Type 都會創建自己的 assemble<name> 任務, 但 Build Variant 是 Build Type 和 Product Flavor 的結合
當使用 Product Flavor 時, 會創建更多assemble類型的任務. 有如下這下:
- assemble<Variant Name>
- assemble<Build Type Name>
- assemble<Product Flavor Name>
第一個允許直接構建單獨的variant. 例如 assembleFlavor1Debug
第二個允許根據給定的Build Type構建所有APK. 例如 assembleDebug 會構建 Flavor1Debug 和 Flavor2Debug variant
第三個勻速根據指定flavor構建所有apk. 例如 assembleFlavor1 會構建 Flavor1Debug 和 Flavor1Release variant.
assemble 任務會構建所有可能的variant.
多flavor variant
在某些情況下, 你可能想要為同一個app根據多種要求創建多種版本的app.
例如, 在Google Play所支持的multi-apk可以支持4中不同的過濾器.
為每種過濾器創建不同的apk可以通過使用多種Product Flavor實現.
假設一個游戲會有演示版本和付費版本, 并且希望使用multi-apk支持的ABI鍋爐汽. 3種ABI和2個版本的應用, 需要生成6個apk
然而, 付費版本的代碼對于3種ABI是相通的, 所以簡單的創建6個flavor是不合適的.
此外, 現在有2種flavor, variant應該自動構建所有可能的組合.
這一功能可以通過Flavor Dimension實現. Flavor被指定為一個具體的dimension:
android {
...
flavorDimensions "abi", "version"
productFlavors {
freeapp {
dimension "version"
...
}
paidapp {
dimension "version"
...
}
arm {
dimension "abi"
...
}
mips {
dimension "abi"
...
}
x86 {
dimension "abi"
...
}
}
}
</code></pre>
android.flavorDimensions 數組定義了可能的dimension, 同時也定義了順序. 每個定義的 Product Flavor 都被指定給了一個dimension
通過以下dimension定義的 Product Flavor [freeapp, paidapp] 和[x86, arm, mips]和[debug, release] Build Type , 會創建以下的build variant:
- x86-freeapp-debug
- x86-freeapp-release
- arm-freeapp-debug
- arm-freeapp-release
- mips-freeapp-debug
- mips-freeapp-release
- x86-paidapp-debug
- x86-paidapp-release
- arm-paidapp-debug
- arm-paidapp-release
- mips-paidapp-debug
- mips-paidapp-release
android.flavorDimensions 定義的dimension的順序非常重要.
每個variant通過多個 Product Flavor 對象進行配置:
- android.defaultConfig
- abi dimension中定義的一個
- version dimension中定義的一個
dimension的順序會決定哪個flavor會覆蓋其他flavor, 當flavor中的某個值替換了低級別flavor中的值的情況下, 對于resource的影響還是比較重要的.
先定義的flavor具有更高的優先級. 所以在本例中:
abi > version > defaultConfig
多flavor項目同時會有額外的sourceset, 與variant sourceset類似, 但不會包括build type:
- android.sourceSets.x86Freeapp
位于 src/x86Freeapp/
- android.sourceSets.armPaidapp
位于 src/arcPaidapp/
- etc…
這允許在flavor組合級別進行自定義. 他們比基本的flavor sourceset用用更高的優先級, 但低于build type sourceset的優先級.
測試
<p>@todo</p>
BuildConfig
在編譯時期, Android Studio會生成一個叫做 BuildConfig 的類, 它包含了構建具體variat的常量值. 你可以在不同的variant中通過檢查這些常亮來改變行為, 例如:
private void javaCode(){
if (BuildConfig.FLAVOR.equals("paidapp")) {
doIt();
} else {
showOnlyInPaindAppDialog();
}
}
以下是BuildConfig包含的值:
- boolean DEBUG : 如果構建時可調式的
- int VERSION_CODE
- String VERSION_NAME
- String APPLICATION_ID
- String BUILD_TYPE : build type的名稱, 例如”release”
- String FLAVOR : flavor名稱, 例如: “paidapp”
如果項目使用flavor dimension, 會生成額外的值:
- String FLAVOR = "armFreeapp"
- String FLAVOR_abi = "arm"
- String FLAVOR_version = "freeapp"
過濾Variant
當你增加了dimension和flavor, 你可能會創建一些沒有意義的variant. 例如, 你可能定義了一個flavor來使用web api, 另一個flavor使用寫死的假數據用于快速測試. 第二種flavor只在開發時有用. 你可以使用 variantFilter 閉包來刪除這個variant:
android {
productFlavors {
realData
fakeData
}
variantFilter {
variant -> def names = variant.flavors*.name
if (names.contians("fakeData") && variant.buildType.name == "release") {
variant.ignore = true
}
}
}
</code></pre>
通過以上配置, 你的項目只有3個variant:
- realDataDebug
- realDataRelease
- fakeDataDebug
高級構建自定義
運行ProGuard
ProGuard插件在Android插件中會自動應用, 如果 Build Type 通過配置 minifyEnabled 屬性啟用了ProGuard后, 任務會自動創建.
android {
buildTypes {
release {
minifyEnalbled true
proguardFile getDefualtProguardFile('proguard-android.txt')
}
}
productFlavors {
flavor1 {
}
flavor2 {
proguardFile 'some-other-rules.txt'
}
}
}
</code></pre>
Variant會使用build type和product flavor中聲明的所有規則文件
有2個默認的規則文件:
- proguard-android.txt
- proguard-android-optimize.txt
他們位于SDK中. 使用 getDefualtProguardFile() 可以返回文件的完整路徑.
壓縮資源
你可以在構建時自動刪除沒有使用的資源文件.
操作任務
基本的Java項目有多個任務一起工作來創建一個輸出.
classes 任務用于編譯Java源代碼. 通過在腳本中使用 classes 可以方便地訪問 build.gradle . 這是 project.tasks.classes 的快捷方式
在Android項目中, 這可能會復雜一些. 因為可能會有很大數量的相同任務, 并且他們的名字是根據 Build Type 和 Product Flavor 來生成的.
為了修復這一問題, 在 android 對象中有兩個屬性:
- applicationVariants (只用于app插件)
- labraryVariants (只用于library插件)
- testVariants (用于兩種插件)
他們會返回 ApplicationVariant , LibraryVariant , 和 TestVariant 相應的 DomainObjectColletion
注意, 訪問任意的collection都會觸發所有任務的創建. 也就是說在訪問collection后不能再進行配置.
DomainObjectCollection 為所有項目提供了直接訪問或通過過濾器訪問的方式
android.applicationVariants.all {
variant ->
...
}
三種variant類都共享以下屬性:
屬性名
屬性類型
描述
name
String
variat名稱. 保證唯一.
description
String
人類可讀的variant描述.
dirName
String
variant子目錄名稱. 保證唯一. 可能會有多個目錄, 例如“debug/flavor1”
baseName
String
variant輸出的基本名稱. 保證唯一
outputFile
File
variant輸出. 是讀/寫屬性
processManifest
ProcessManifest
處理Manifest的任務.
aidlCompile
AidlCompile
編譯AIDL文件的任務.
renderscriptCompile
RenderscriptCompile
編譯Renderscript文件的任務.
mergeResources
MergeResources
合并資源的任務
mergeAssets
MergeAssets
合并assets的任務
processResources
ProcessAndroidResources
處理和編譯資源的任務
generateBuildConfig
GenerateBuildConfig
生成BuildConfig類的任務
javaCompile
JavaCompile
編譯Java代碼的任務
processJavaResources|Copy|處理Java資源的任務|
|assemble|DefaultTask|該variant的組裝錨點任務|
ApplicationVariant 類增加了如下屬性:
屬性名
屬性類型
描述
buildType
BuildType
variant的BuildType
productFlavors
List
variant的ProductFlavors. 可以為空單永不為null.
mergedFlavor
ProductFlavor
android.defaultConfig和variant.productFlavors的合并
signingConfig
SigningConfig
該variant使用的SigningConfig對象
isSigningReady
boolean
true如果variant擁有所有簽名需要的信息
testVariant
BuildVariant
測試該variant的TestVariant
dex
Dex
dex代碼的任務. 如果variant是一個libaray則為null
packageApplication
PackageApplication
構建最終apk的任務. 如果variant是一個library則為null
zipAlign
ZipAlign
zipalign apk的任務. 如果variant是一個library或apk無法簽名時為null
install
DefaultTask
安裝任務, 可以為null
uninstall
DefaultTask
卸載任務
LibraryVariant 類增加了以下屬性:
屬性名
屬性類型
描述
buildType
BuildType
variant的BuildType
mergedFlavor
ProductFlavor
defaultConfig值
testVariant
BuildVariant
測試該variant的Build Variant
packageLibrary
Zip
打包Library的arr文件的任務. 如果不是library時為null
TestVariant 增加了以下屬性:
屬性名
屬性類型
描述
buildType
BuildType
variant的BuildType
productFlavors
List
variant的ProductFlavors可以為空但永遠不為null
mergedFlavor
ProductFlavor
android.defaultConfig和variant.productFlavors的合并
signingConfig
SigningConfig
該variant使用的SigningConfig對象
isSigningReady
boolean
true如果該variant有所有簽名需要的信息
testedVariant
BaseVariant
該TestVariant測試用的BaseVariant
dex
Dex
dex代碼的任務. 如果variant是一個library則為null.
packageApplication
PackageApplication
構建最終apk的任務. 如果variant是一個library則為null
zipAlign
ZipAlign
zipalign apk的任務. 如果variant是一個library或apk無法簽名則為null
install
DefaultTask
安裝任務, 可以為null
uninstall
DefaultTask
卸載任務
connectedAndroidTest
DefaultTask
在已連接的設備上運行android測試的任務
providerAndroidTest
DefaultTask
使用擴展API運行android測試的任務
Android具體任務類型的API:
- ProcessManifest
- File manifestOutputFile
</li>
- AidlCompile
- File sourceOutputDir
</ul> </li>
- RenderscriptCompile
- File sourceOutputDir
- File resOutputDir
</ul> </li>
- MergeResources
- File outputDir
</ul> </li>
- MergeAssets
- File outputDir
</ul> </li>
- ProcessAndroidResources
- File manifestFile
- File resDir
- File assetsDir
- File sourceOutputDir
- File textSymbolOutputDir
- File packageOutputFile
- File proguardOutputFile
</ul> </li>
- GenerateBuildConfig
- File sourceOutputDir
</ul> </li>
- Dex
- File outputFolder
</ul> </li>
- PackageApplication
- File resourceFile
- File dexFile
- File javaResourceDir
- File jniDir
- File outputFile
- 若要改變最終輸出文件, 可在variant對象中直接調用”outputFile”
</ul> </li>
</ul> </li>
- ZipAlign
- File inputFile
- File outputFile
- 若要改變最終輸出文件, 可在variant對象中直接調用”outputFile”
</ul> </li>
</ul> </li>
</ul>
每種任務類型的api會由于Gradle工作方式和Android插件的設置而有所限制
首先, Gradle只希望配置輸入/輸出位置和可能的可選標志. 所以在此任務只在輸入/輸出定義.
第二, 大部分任務的輸出都是不重要的, 它們大部分都來自 srouceSets , Build Type , Product Flavor 的混合值. 為了保持構建文件的簡潔易懂, 我們的目標是讓開發者通過DSL來修改構建, 而不是深入輸出和任務選項來修改它們.
注意, 除了ZipAlign任務, 其他所有任務類型都需要設置私有數據才能正常工作. 這意味著不能手動創建這些類型的任務.
設置語言級別
你可以使用 compileOptions 塊來選擇編譯器的語言級別. 默認會使用 compileSdkVersion 的值
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_6
targetCompatitility JavaVersion.VERSION_1_6
}
}
來自:http://blog.lixplor.com/2016/12/15/android-gradle-script/