1 簡介
這篇文檔是基於0.9版本的Gradle插件,1.0以前的版本由於不兼容,可能會有所不同
1.1 新的構建系統的目標
新構建系統的目標是:
- 使得代碼和資源的重用更加簡單
- 使得創建同一應用程序的不同版本更加容易,不管是多個apk版本還是同一版本的多種定製
- 使得配置,擴展和自定義構建更加容易
- 良好的IDE集成
1.2 爲什麼使用Gradle
Gradle是一個高級構建系統和構建工具,允許通過插件自定義構建邏輯
以下一些功能使得我們選擇Gradle:
- 使用特定領域語言(DSL)來描述和控制構建邏輯
- 構建腳本基於Groovy語言,允許通過DSL混合元素聲明和通過代碼控制DSL元素,來產生自定義的構建邏輯
- 支持Maven和(或者)Ivy管理依賴
- 非常靈活。允許使用最佳實踐,但也不強制自己的實現方式
- 插件能夠提供自己的DSL和API供構建腳本使用
- 提供優秀的工具API以供IDE集成
2 環境要求
- Gradle 1.10或者1.11或者1.12,以及插件版本0.11.1
- SDK以及Buid Tools 19.0.0,某些功能可能需要更新的版本
3 基本項目
Gradle項目通過項目根目錄下的 build.gradle 文件來描述構建過程
3.1 簡單的構建文件
最簡單的Java項目構建文件 build.gradle
這個腳本應用了Gradle的Java插件。這個插件了提供構建和測試Java應用的所有功能
最簡單的Android項目的構建文件包含以下內容:
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.11.1'
}
}
apply plugin: 'android'
android {
compileSdkVersion 19
buildToolsVersion "19.0.0"
}
Note(譯註): 最新的android插件聲明
apply plugin: 'com.android.application'
在這個Android構建腳本里包含了三個主要內容:
buildscript { ... }
配置了驅動構建過程的代碼。在這個案例中,聲明瞭使用Maven倉庫,以及一個Maven文件(artifact)的依賴路徑。這個文件就是包含了Android
Gradle插件的庫,版本爲0.11.1
然後,android
插件被應用,像之前的Java插件一樣
最後,android { ... }
配置了anroid構建過程需要的參數。這也是Adnroid DSL的入口。默認的情況下,只有編譯目標SDK版本,和構建工具版本是必須的。在腳本中,對應的是compileSdkVersion
和buildtoolsVersion
屬性。compileSdkVersion
和舊編譯系統中project.properties
文件中的target
屬性對應。這個新屬性compileSdkVersion
可以是一個int值(API
Level)或者一個和之前的target
屬性值一樣的字符串
重點: 你應該只應用android插件,同時應用java插件會導致構建錯誤
注意: 你同樣需要一個local.properties
文件來指明SDK的路徑,和build.gradle
在同一路徑下,在文件中使用sdk.dir
屬性指明。或者,你可以設置ANDROID_HOME
環境變量。兩者是一致的,你可以選擇一種你喜歡的方式。
3.2 項目結構
前面的android構建腳本使用了默認的文件夾目錄結構。Gradle遵循約定優於配置
的原則,在可能的情況下提供了合理的默認配置參數。
基本的項目包含兩個“source sets”組件。main source code
和test
code
,位於以下的目錄中:
在這些目錄中,都存在目錄對應源碼組件
不管是Java還是Android插件,源碼目錄和資源目錄都如下 :
對於Android插件,還有特有的文件和目錄
Note: src/androidTest/AndroidManifest.xml
不是必須的,會自動被創建。
3.2.1 配置項目結構
當默認項目結構不合適的時候,可以配置項目目錄。根據Gradle文檔,可以通過下面的腳本重新配置Java項目的sourceSets:
sourceSets {
main {
java {
srcDir 'src/java'
}
resources {
srcDir 'src/resources'
}
}
}
Note: srcDir 會添加指定的目錄到源文件目錄列表中(這在Gradele文檔中沒有提及,但是實際上是這樣的)。
爲了替換默認的源文件目錄列表,可以使用srcDirs
來指定目錄數組。這也是一種不同的使用方式:
sourceSets {
main.java.srcDirs = ['src/java']
main.resources.srcDirs = ['src/resources']
}
更多的信息,參考Gradle文檔中的Java插件內容
Android插件使用相似的語法,但是由於使用是自己的sourceSets
,相應的目錄在(build.gradle
文件中的)android對象中指定
下面是一個示例,它使用舊項目的源碼結構,並且將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')
}
}
Note: 由於舊的結構將所有的源文件 (java, aidl, renderscript, and java資源文件)放在一個目錄裏,我們需要映射這些sourceSet組件到src目錄。
Note: setRoot() 方法將整個sourceSet(包含子目錄)指向新的目錄。比如上面,將src/androidTest/*
指向了 tests/*
以上是Android特有的,如果配置在Java sourceSets中就沒有作用
‘migrated’ 示例(位於本頁面底部)中展示了這部分內容
3.3 構建任務
3.3.1 通用任務
在build文件中應用一個插件將自動創建一系列構建任務。Java插件和Android插件都是這樣。任務約定如下:
- assemble
組合項目輸出
- check
執行所有檢查
- build
執行assemble和check兩個task的所有工作
- clean
清理項目輸出
任務assemble
, check
和 build
不會做任何實際的事情。他們只是錨點任務(anchor
tasks),插件依賴他們來添加實際執行實際操作的任務。
這樣就不需要考慮項目是什麼類型,使用的是什麼插件,都可以執行同樣的任務。
例如,使用findbugs插件,會創建新的任務,並讓check
依賴這個任務,使得check
被調用時這個任務就會被調用。
在終端(命令行,gradle項目目錄下)中運行下面的任務可以查詢到高級別的任務:
運行以下命令可以看到全部任務和任務依賴關係:
Note: Gradle自動監視一個任務聲明的輸入輸出文件。再次執行構建任務時,如果文件沒有改變,Gradle會指明所有任務爲UP-TO-DATE
,意味着任務不需要執行。這樣的話,任務可以正確地互相依賴,而不不會導致非必須的構建操作
3.3.2 Java項目的任務
Java插件主要創建兩個任務,下面是這兩個錨點任務的依賴關係
- assemble
- jar
這個任務創建輸出
- jar
- check
- test
這個任務運行測試
- test
jar
任務直接或間接地依賴其他任務:例如classes
任務將編譯Java源碼
testClasses
任務用於編譯測試的,但是這個任務很少被調用,因爲test
任務依賴於它(就像依賴classes
任務一樣)
通常來說,你只需要調用assemble
或者check
任務,而不需要調用其他任務。
你可以在Gradle Java插件文檔看到Java插件的全部任務和它們的描述
3.3.3 Android任務
Android插件使用同樣的約定來保持和其他插件的兼容,並且添加了額外的錨點任務:
-
assemble
這個任務組織項目的輸出 -
check
這個項目運行所有檢查 -
connectedCheck
運行檢查需要一個已連接的設備或者模擬器。並在所有已連接的設備上異步運行。 -
deviceCheck
通過APIs連接遠程設備並運行檢查。這通常在CI服務器上運行。 -
build
運行assemble
和check
-
clean
清理項目輸出
新的錨點任務是必須的,以保證在不需要設備連接的情況下能運行常規檢查。
需要注意的是,build
任務並不依賴deviceCheck
或者connectedCheck
一個Android項目至少有兩個輸出:debug APK 和 release APK。它們每一個都有自己的錨點任務來幫助它們完成獨立的構建:
- assemble
- assembleDebug
- assembleRelease
它們都依賴其它任務來完成構建一個apk所需要的多個步驟。assemble
任務依賴這兩個任務,所以調用assemble
會生成兩個APK。
Tip: Gradle支持在命令行中使用camel形式的任務名縮寫。
例如:
gradle aR
和gradle assembleRelease
是一樣的,因爲沒有別的任務名有同樣的縮寫
錨點任務check
也有自己的依賴:
- check
- lint
- connectedCheck
- connectedAndroidTest
- connectedUiAutomatorTest (not implemented yet)
- deviceCheck
- 依賴於當其它插件實現測試擴展點時所創建的任務。
最終,插件會爲所有構建類型(debug, release, test)創建install
/uninstall
任務,如果輸出文件可以安裝的話(必須簽名)。
3.4 基本的構建過程定製
Android插件提供了大量DSL來直接從構建系統中定製大多數事情。
3.4.1 Manifest屬性
通過DSL,可以配置以下manifest屬性:
- minSdkVersion
- targetSdkVersion
- versionCode
- versionName
- applicationId (實際的packageName – 前往 ApplicationId versus PackageName 查看更多)
- Package Name for the test application
- Instrumentation test runner
例如:
android {
compileSdkVersion 19
buildToolsVersion "19.0.0"
defaultConfig {
versionCode 12
versionName "2.0"
minSdkVersion 16
targetSdkVersion 16
}
}
配置項位於android
元素中的defaultConfig
元素中。
之前版本的Android Plugin使用packageName來配置manifest文件的packageName
屬性。從0.11.1版本開始,你應該在build.gradle文件使用applicationId來配置manifest文件的packageName
屬性。
這是爲了消除Android應用的packageName
(作爲Android應用的ID)和java包名之間的疑義。
在構建文件中定義的強大之處在於它可以是動態的。
例如,可以從一個文件中讀取版本名稱,或者使用自定義的邏輯:
def computeVersionName() {
...
}
android {
compileSdkVersion 19
buildToolsVersion "19.0.0"
defaultConfig {
versionCode 12
versionName computeVersionName()
minSdkVersion 16
targetSdkVersion 16
}
}
Note: 函數名不要與指定範圍內已經存在的getter方法名衝突。例如,在defaultConfig { ...}
中調用getVersionName()會自動使用defaultConfig.getVersionName(),而不是你自定義的其它getVersionName()。
如果屬性沒有通過DSL設置,那麼默認的屬性值會被使用。下面是默認的屬性值列表:
Property Name | Default value in DSL object | Default value |
---|---|---|
versionCode | -1 | value from manifest if present |
versionName | null | value from manifest if present |
minSdkVersion | -1 | value from manifest if present |
targetSdkVersion | -1 | value from manifest if present |
applicationId | null | value from manifest if present |
testApplicationId | null | applicationId + “.test” |
testInstrumentationRunner | null | android.test.InstrumentationTestRunner |
signingConfig | null | null |
proguardFile | N/A (set only) | N/A (set only) |
proguardFiles | N/A (set only) | N/A (set only) |
如果你在構建腳本中使用自定義的邏輯讀取這些屬性,那麼第二列的屬性就很重要。例如,你可能這樣寫:
if (android.defaultConfig.testInstrumentationRunner == null) {
// assign a better default...
}
如果這個值是null,那麼在構建過程中會被第三列的默認值替代,但是DSL元素不會包含這個默認值(第三列的值),所以你查詢不到這個值。這是爲了防止解析應用的manifest文件,除非真的必要。
3.4.2 構建類型(Build Types)
默認情況下,Android插件會自動設置項目同時構建debug和release版本的應用程序。
這兩個版本的不同之處主要在於能否在一個安全設備上調試程序,和APK如何簽名。
debug版本使用一個自動創建的密鑰/證書,並使用已知的name/password來簽名(防止構建過程中出現請求提示)。release版本在構建過程中沒有簽名,需要稍後簽名。
這些配置通過一個構建類型(BuildTpye)對象來設置。默認情況下,debug和release這兩個構建類型都會被創建。
Android插件允許自定義這兩個實例,也允許創建其它構建類型。這些都在buildTypes的DSL容器中配置:
android {
buildTypes {
debug {
applicationIdSuffix ".debug"
}
jnidebug.initWith(buildTypes.debug)
jnidebug {
packageNameSuffix ".jnidebug"
jniDebuggable true
}
}
}
上面的代碼片段完成來以下功能:
-
配置默認的
debug
構建類型:- 將包名設置成
<app appliationId>.debug
,以便在同一設備上同時安裝debug和release版本
- 將包名設置成
-
創建了一個新的名爲
jnidebug
的構建類型,是debug構建類型的一個副本 - 配置
jnidebug
構建類型,允許調試JNI組件,並且添加一個不同的包名後綴
創建一個新的構建類型就像在buildTypes
容器中使用一個新的元素一樣簡單,可以通過調用initWith()
或者使用閉包來配置
以下是可能用到的屬性和它們的默認值:
Property name | Default values for debug | Default values for release / other |
---|---|---|
debuggable | true | false |
jniDebuggable | false | false |
renderscriptDebuggable | false | false |
renderscriptOptimLevel | 3 | 3 |
applicationIdSuffix | null | null |
versionNameSuffix | null | null |
signingConfig | android.signingConfigs.debug | null |
zipAlignEnabled | false | true |
minifyEnabled | false | false |
proguardFile | N/A (set only) | N/A (set only) |
proguardFiles | N/A (set only) | N/A (set only) |
除了以上這些屬性,Build Types還可以通過源碼和資源來影響構建過程。
每一個構建類型都會創建一個匹配的sourceSet,默認的路徑爲:
這意味這新的構建類型的名字不能是main或者androidTest(這是插件強制要求的),而構建類型的名稱必須是唯一的。
像其它sourceSet一樣,構建類型的sourceSet可以重新被定向:
android {
sourceSets.jnidebug.setRoot('foo/jnidebug')
}
另外,每一個Build Type都會創建一個assemble<BuildTypeName>
任務。
在前面,assembleDebug
和assembleRelease
已經提到過了,這就是它們的來源。當debug和release構建類型被預創建的時候,它們相關的任務就被自動創建了,比如assembleDebug
和assembleRelease
上面的build.gradle片段同樣會創建assembleJnidebug
任務,assemble
會像依賴assembleDebug
和assembleRelease
任務一樣依賴assembleJnidebug
。
Tip: 你可以在命令行下輸入gradle aJ
來運行assembleJnidebug
任務。
可能用到的場景:
- 只有debug模式才需要的權限,而release模式不需要
- 自定義debug實現
- debug模式使用不同的資源(例如,資源取值與簽名證書綁定)
BuildType的源碼和資源通過以下方式使用:
- manifest文件合併到app的manifest文件中
- 源碼作爲另一個源碼目錄
- 資源疊加到main的資源中,取代已經存在的值
3.4.3 簽名配置
對一個應用程序簽名需要以下:
- 一個keystore
- 一個keystore密碼
- 一個key的別名
- 一個key密碼
- 存儲類型
位置,別名,兩個密碼和存儲類型一個組成一個簽名配置(SigningConfig)
默認情況下,debug
簽名配置使用一個debug keystore,已知的密碼和已知的別名以及別名密碼。
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 {
debuggable true
jniDebuggable true
signingConfig signingConfigs.myConfig
}
}
}
上面的片段修改debug keystore的位置到項目根目錄下。這會影響任何使用它的構建類型,在這個案例中,受影響的是debug
構建類型。
這裏也創建了一個新的簽名配置和一個使用這個新簽名配置的行的構建類型。
Note: 只有默認路徑下debug keystore不存在的時候會被自動創建。改變debug keystore的路徑則不會在新的路徑下自動創建debug keystore。創建一個名字不同的簽名配置,但是使用默認的debug keystore路徑,會自動創建debug keystore。也就是說,是否自動創建debug keystore,是由keystore的位置決定,而不是配置的名稱。
Note: keystore的路徑通常是項目根目錄的相對路徑,但是也可以使用絕對路徑,儘管不推薦這樣(debug keystore除外,因爲它會自動被創建)。
**Note: 如果你要把這些文件添加到版本控制系統中,你可能不想把密碼寫在文件中。下面的Stack Overflow連接提供了從從控制檯或者環境變量讀取的方法。
http://stackoverflow.com/questions/18328730/how-to-create-a-release-signed-apk-file-using-gradle
我們以後會更新指南,提供更多的細節**
3.4.4 運行ProGuard
ProGuard從Gradle plugin for ProGuard 4.10開始支持的(since Gradle plugin 0.4)。如果構建類型的minifyEnabled
屬性被設置爲true,那麼Progruard插件會自動被添加進來,對應的任務也自動被創建。
Note: 從Gradle插件版本0.14.0開始BuildType.runProguard更改爲minifyEnabled屬性。具體請參考Release notes
android {
buildTypes {
release {
minifyEnabled true
proguardFile getDefaultProguardFile('proguard-android.txt')
}
}
productFlavors {
flavor1 {
}
flavor2 {
proguardFile 'some-other-rules.txt'
}
}
}
Variant會使用所有聲明的規則文件,包括聲明在相應的Build Type和flavor中的。
SDK中有兩個默認的規則文件:
- proguard-android.txt
- proguard-android-optimize.txt
它們位於sdk路徑下,使用getDefaultProguardFile()可以獲取文件的完整路徑。它們除了是否要進行優化之外,其它都是相同的。
3.4.5 壓縮資源文件
構建時可以自動移除沒有被使用的資源文件。更多詳細信息請查看文檔資源文件壓縮
4 依賴關係,Android庫項目和多項目設置
Gradle項目可以依賴其它組件,這些組件可以是外部二進制包,或者其它Gradle項目。
4.1 依賴二進制包
4.1.1 本地包
爲了配置一個外部庫jar依賴,你需要在compile
配置中添加一個依賴
dependencies {
compile files('libs/foo.jar')
}
android {
...
}
Note: dependencies
DSL元素是標準Gradle API的一部分,並不屬於android
元素。
compile
配置是用來編譯main應用的。任何添加到編譯路徑中的東西都會被打包到最終的apk文件中。
下面是其它一些在添加依賴時可能用到的配置:
compile
: 主moduleandroidTestCompile
: 測試moduledebugCompile
: debug構建類型的編譯releaseCompile
: release構建類型的編譯
因爲構建一個apk必然有一個相關的構建類型,所以apk通常至少有兩個編譯配置:compile
和<buildtype>Compile
創建一個構建類型時會自動創建一個基於它名字的編譯配置<buildtype>Compile
當你在debug版本里需要使用一個自定義庫(例如記錄crash信息),而release版本不需要,或者他們依賴同一個庫的不同版本的時候,會非常有用。
也可以通過添加一個目錄來依賴目錄下的所有jar文件:
compile fileTree(dir: 'libs', include: ['*.jar'])
4.1.2 遠程文件
Gradle支持從Maven或者Ivy倉庫獲取依賴文件。
首先,必須把倉庫添加到列表中,其次,必須按照Maven或者Ivy的文件聲明規範來聲明依賴。
repositories {
mavenCentral()
}
dependencies {
compile 'com.google.guava:guava:11.0.2'
}
android {
...
}
Note: mavenCentral()
是指定倉庫URL的便捷方式。Gradle支持遠程和本地倉庫。
Note: Gradle遵循依賴關係的傳遞性。如果一個被依賴文件也依賴其它文件,那些被依賴的文件也會被拉取下來。
更多關於配置依賴的信息,請查看Gradle用戶指南和DSL文檔
4.2 多項目設置
Gradle項目可以通過多項目設置依賴其它gradle項目。
一個多項目設置通常把所有子項目作爲子目錄放在指定的項目根目錄下。
例如,項目結構如下:
MyProject/
+ app/
+ libraries/
+ lib1/
+ lib2/
我們在這個結構中定義3個項目。Gradle將通過以下名字引用它們:
:app
:libraries:lib1
:libraries:lib2
每個項目都有自己的build.gradle
文件,聲明來它怎樣構建。另外,在根目錄下還有一個settings.gradle
文件,聲明瞭所有的子項目。
目錄結構如下:
MyProject/
| settings.gradle
+ app/
| build.gradle
+ libraries/
+ lib1/
| build.gradle
+ lib2/
| build.gradle
settings.gradle
文件的內容十分簡單:
include ':app', ':libraries:lib1', ':libraries:lib2'
指明哪個文件夾是一個實際的Gradle項目。
:app
項目或許依賴其它庫項目,那麼依賴關係聲明如下:
dependencies {
compile project(':libraries:lib1')
}
更多關於多項目設置的信息在這裏。
4.3 庫項目
在上面的多項目設置中,:libraries:lib1
和:libraries:lib2
可能是Java項目,:app
Android項目將會使用它們輸出的jar文件。
然而,如果你想要共享使用了Android API或者Android資源文件的代碼(在庫項目中使用了Android API或Android資源文件),這些庫項目就不能是常規的Java項目,必須是Android庫項目。
4.3.1 創建一個庫項目
一個庫項目和常規的Android項目很相似,只有很少的區別。
因爲構建庫項目和構建應用程序不一樣,所以使用不同的插件。構建庫項目的插件和構建應用程序的插件在內部共享大部分的代碼,並且它們都是由com.android.tools.build.gradle
jar庫提供。
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.5.6'
}
}
apply plugin: 'android-library'
android {
compileSdkVersion 15
}
這是一個使用API 15編譯的庫項目。SourceSets
和依賴關係的處理跟應用程序項目中一樣,而且定製方式也一樣。
4.3.2 普通項目和庫項目的區別
一個庫項目的主要輸出是一個.aar
包(它代表Android的歸檔文件)。它包含編譯好的源碼(例如jar文件或者本地.so文件)以及資源文件(manifest, res, assets)。
一個庫項目也可以生成一個測試apk來測試,而不依賴應用程序。
由於使用同樣的錨點任務(assembleDebug
, assembleRelease
),所以在命令行中構建庫項目和普通項目沒有區別。
其餘部分,庫項目和應用程序項目一樣。都有構建類型和product flavors,可以生成多個版本的aar。
要注意的是,多數Build Type配置不適用於庫項目。然而,你可以定製sourceSet
來改變所依賴庫的內容,不論它是被普通項目使用還是被測試。
4.3.3 引用一個庫項目
引用一個庫項目和引用其它項目的方式一樣:
dependencies {
compile project(':libraries:lib1')
compile project(':libraries:lib2')
}
Note: 如果你由多個庫項目,那麼順序是很重要的。這和舊構建系統中的project.properties
文件中的依賴順序一樣重要。
4.3.4 庫項目發佈
默認的情況下,庫項目只會發佈release變種版本(release variant)。這個版本會被所有引用了庫項目的項目使用,不管它們自己構建的是什麼版本。這是Gradle導致的限制,我們正努力消除這個限制。
可以如下控制哪個版本會被髮布:
android {
defaultPublishConfig "debug"
}
要注意的是,這個發佈配置名稱必須是完整的variant名稱,release
和debug
這兩個名稱只有在沒有flavor的時候才使用。如果想要在有flavor的時候改變默認的發佈版本,你必須這樣寫:
android {
defaultPublishConfig "flavor1Debug"
}
發佈庫項目的所有版本也是可能的。我們計劃在普通的項目依賴項目的工程中允許這種做法,但是由於Gradle的限制,現在還不能這麼做(我們也在努力修復這個問題)。
發佈所有版本的功能默認沒有開啓。開啓如下:
android {
publishNonDefault true
}
必須認識到發佈多個variant版本意味着發佈多個aar文件,而不是在一個aar文件中包含了多個variant版本。每一個aar文件就是一個獨立的variant。
發佈一個variant版本意味着構建出了一個可用的aar文件,作爲Gradle項目的輸出文件。這個文件可以發佈到maven倉庫,或者在其他項目依賴該庫項目時作爲依賴目標。
Gradle有默認文件的概念。下面這個就使用了默認文件:
compile project(':libraries:lib2')
爲了依賴其他的發佈版本,你必須指定具體使用哪一個:
dependencies {
flavor1Compile project(path: ':lib1', configuration: 'flavor1Release')
flavor2Compile project(path: ':lib1', configuration: 'flavor2Release')
}
重要: 要注意已發佈的配置是完整的variant版本,包含了構建類型,因此引用的時候也必須是完整的。
重要: 當開啓了無默認版本發佈,Maven發佈插件會把這些額外的版本作爲擴展包(按分類器)發佈。這意味着並不是真正兼容地發佈到maven倉庫。你應該發佈一個獨立的vatiant到倉庫,或者開啓發布所有配置來支持跨項目依賴。
5 測試
構建一個測試應用已經內置在應用項目內。不需要再創建單獨的測試項目。
5.1 單元測試
試驗性的單元測試功能支持已經加入到1.1中,具體請看這個頁面。本節其他部分講述的是”instrumentation tests”
5.2 基礎和配置
正如前面提到的,緊鄰着main
sourceSet 的就是 androidTest
sourceSet,默認在src/androidTest/
路徑下。
從這個sourceSet 會構建出一個使用Android測試框架,並且可以部署到設備上的測試apk來測試應用程序。這裏面可以包含單元測試,集成測試,和後續UI自動化測試。
測試應用的<instrumentation>
節點是自動生成的,但是你也可以創建一個src/androidTest/AndroidManifest.xml
,並在這個manifest文件中添加其他組件。
下面是一些測試應用可以配置的值:
- testPackageName
- testInstrumentationRunner
- testHandleProfiling
- testFunctionalTest
正如前面所看到的,這些在defaultConfig對象中配置:
android {
defaultConfig {
testPackageName "com.test.foo"
testInstrumentationRunner "android.test.InstrumentationTestRunner"
testHandleProfiling true
testFunctionalTest true
}
}
在測試應用程序的manifest文件中,instrumentation節點的targetPackage屬性值會自動使用測試應用的package名稱設置,即使這個名稱是通過defaultConfig或者Build Type對象自定義的。這也是manifest文件需要自動生成的一個原因。
另外,這個測試sourceSet也可以擁有自己的依賴。
默認情況下,應用程序和他的依賴會自動添加的測試應用的classpath中,但是也可以通過以下來擴展:
dependencies {
androidTestCompile 'com.google.guava:guava:11.0.2'
}
測試應用通過assembleTest
任務來構建。assembleTest不依賴於main中的assemble
任務,需要手動設置運行,不能自動運行。
目前只有一個Build Type被測試。默認情況下是debug
Build Type,但是這也可以通過以下自定義配置:
android {
...
testBuildType "staging"
}
5.3 運行測試
正如前面提到的,檢查通過錨點任務connectedCheck
啓動,這需要一個設備已連接。
這個過程依賴於androidTest任務,因此將會運行androidTest。這個task將會執行下面內容:
- 確認應用和測試應用都被構建(依賴於
assembleDebug
和assembleTest
) - 安裝這兩個應用
- 運行這些測試
- 卸載這兩個應用.
如果有多於一個連接設備,那麼所有測試都會同時運行在所有連接設備上。如果其中一個測試失敗,不管是哪一個設備,這個構建就失敗。
所有測試結果都被保存爲XML文檔,路徑爲:
build/androidTest-results
(這和常規的JUnit類似,運行結果保存在build/test-results)
同樣,這也可以自定義配置:
android {
...
testOptions {
resultsDir = "$project.buildDir/foo/results"
}
}
android.testOptions.resultsDir
由Project.file(String)獲得。
5.4 測試Android庫
測試Android庫項目的方法與應用項目的測試方法基本一樣。
唯一的不同在於整個庫(包括它的依賴)都是自動作爲依賴庫被添加到測試應用中。結果就是測試APK不單隻包含它的代碼,還包含了庫項目自己和庫的所有依賴。
庫的manifest被組合到測試應用的manifest中(和其他項目引用這個庫時一樣)。
androidTest
變成只是安裝(或者卸載)測試APK(因爲沒有其它APK要安裝)。
其它的部分都是類似的。
5.5 測試報告
當運行單元測試的時候,Gradle會輸出一份HTML格式的報告以方便查看結果。
Android plugin也是基於此,並且擴展了HTML報告文件,它將所有連接設備的報告都合併到一個文件裏面。
5.5.1 獨立項目
項目將會自動生成測試運行,測試報告默認位置:
build/reports/androidTests
這非常類似於JUnit的報告所在位置build/reports/tests
,其它的報告通常位於build/reports/<plugin>/
這個路徑也可以通過以下方式自定義:
android {
...
testOptions {
reportDir = "$project.buildDir/foo/report"
}
}
報告將會合並運行在不同設備上的測試結果。
5.5.2 多項目測試報告
在一個配置了多個應用或者多個庫項目的項目中,當同時運行所有測試的時候,生成一個單一報告文件記錄所有的測試可能是非常有用的。
爲了實現這個目的,需要使用同一個依賴文件中的另一個插件。可以通過以下方式添加:
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.5.6'
}
}
apply plugin: 'android-reporting'
這必須添加到項目的根目錄下,例如與settings.gradle文件同個目錄的build.gradle文件中。
然後,在命令行中導航到項目根目錄下,輸入以下命令就可以運行所有測試併合並所有報告:
gradle deviceCheck mergeAndroidReports --continue
注意:--continue
選項將允許所有測試,即使子項目中的任何一個運行失敗都不會停止。如果沒有這個選項,第一個失敗測試將會終止全部測試的運行,這可能導致一些項目沒有執行過它們的測試。
5.6 Lint支持
從0.7.0版本開始,你可以爲項目中一個特定的variant版本運行lint,也可以爲所有variant版本都運行lint。它將會生成一個報告描述哪一個variant版本中存在着問題。
你可以通過以下lint選項配置lint。通常情況下你只需要配置其中一部分,以下列出了所有可使用的選項:
android {
lintOptions {
// set to true to turn off analysis progress reporting by lint
quiet true
// if true, stop the gradle build if errors are found
abortOnError false
// if true, only report errors
ignoreWarnings true
// if true, emit full/absolute paths to files with errors (true by default)
//absolutePaths true
// if true, check all issues, including those that are off by default
checkAllWarnings true
// if true, treat all warnings as errors
warningsAsErrors true
// turn off checking the given issue id's
disable 'TypographyFractions','TypographyQuotes'
// turn on the given issue id's
enable 'RtlHardcoded','RtlCompat', 'RtlEnabled'
// check *only* the given issue id's
check 'NewApi', 'InlinedApi'
// if true, don't include source code lines in the error output
noLines true
// if true, show all locations for an error, do not truncate lists, etc.
showAll true
// Fallback lint configuration (default severities, etc.)
lintConfig file("default-lint.xml")
// if true, generate a text report of issues (false by default)
textReport true
// location to write the output; can be a file or 'stdout'
textOutput 'stdout'
// if true, generate an XML report for use by for example Jenkins
xmlReport false
// file to write report to (if not specified, defaults to lint-results.xml)
xmlOutput file("lint-report.xml")
// if true, generate an HTML report (with issue explanations, sourcecode, etc)
htmlReport true
// optional path to report (default will be lint-results.html in the builddir)
htmlOutput file("lint-report.html")
// set to true to have all release builds run lint on issues with severity=fatal
// and abort the build (controlled by abortOnError above) if fatal issues are found
checkReleaseBuilds true
// Set the severity of the given issues to fatal (which means they will be
// checked during release builds (even if the lint target is not included)
fatal 'NewApi', 'InlineApi'
// Set the severity of the given issues to error
error 'Wakelock', 'TextViewEdits'
// Set the severity of the given issues to warning
warning 'ResourceAsColor'
// Set the severity of the given issues to ignore (same as disabling the check)
ignore 'TypographyQuotes'
}
}
6 構建不同版本(Build Variants)
新構建系統的一個目標就是爲一個應用構建不同的版本。
有兩個主要的場景:
- 同一個應用的不同版本。例如,免費版和收費版
- 同一個應用,爲了在Google Play Store上發佈並適配多鍾設備,打包出不同的apk。
- 以上兩種情況的綜合
也就是說,從同一個項目中生成這些不同的apk,而不是使用一個庫工程和2個以上的主應用工程。
6.1 產品定製(Product flavors)
一個product flavor
定義了項目構建輸出的一個自定義應用版本。一個單獨項目可以有不同的flavor,來生成不同的應用。
這個新概念(flavor)是用來解決不同應用版本間差異很小的情形。如果“這是否同一個應用?”的回答是肯定的話,這是比使用庫項目更好的做法。
flavor使用productFlavors
這個DSL容器來聲明:
android {
....
productFlavors {
flavor1 {
...
}
flavor2 {
...
}
}
}
這裏創建了兩個flavor,分別是 flavor1
和 flavor2
。
注意: flavor的名字不能喝已有的構建類型(Build Type)名字衝突,或者和androidTest
這個sourceSet的名字衝突。
6.2 構建類型+產品定製=變種版本(Build Type + Product Flavor = Build Variant)
前面已經提到,每一個構建類型都會生成一個apk。忘了的話,請看3.4.2
Product Flavors 也會做同樣的事情,實際上,項目輸出來自所有可能的,Build Types和Product Flavors的組合,如果有Product Flavors的話。
每種Build Types和Product Flavors的組合就是一個Build Variant。
例如,默認的debug
和 release
這兩個Build
Types,和上面創建的兩個flavor會生成4個Build Variants:
- Flavor1 - debug
- Flavor1 - release
- Flavor2 - debug
- Flavor2 - release
沒有flavor的項目也有Build Variants,使用默認的沒有名字的flavor配置,使得Build Variants列表看起來和 Build Types一樣。
6.3 ProductFlavor配置
每個flavor在下面這樣的閉包結構中配置:
android {
...
defaultConfig {
minSdkVersion 8
versionCode 10
}
productFlavors {
flavor1 {
packageName "com.example.flavor1"
versionCode 20
}
flavor2 {
packageName "com.example.flavor2"
minSdkVersion 14
}
}
}
注意到android.productFlavors.*
和android.defaultConfig
的配置項類型相同,這意味着他們共享相同的屬性。
defaultConfig
爲所有的flavor提供默認的配置,每個flavor都可以覆蓋配置項的值。上面的例子中,最終的配置如下:
- flavor1
- packageName: com.example.flavor1
- minSdkVersion: 8
- versionCode: 20
- flavor2
- packageName: com.example.flavor2
- minSdkVersion: 14
- versionCode: 10
通常,Build Type配置會覆蓋其他配置。例如Build Type‘的packageNameSuffix
會添加到Product
Flavor‘的 packageName
上。
也有一些情況下,一個配置項可以同時在Build Type 和 Product Flavor都進行配置,這時,就要具體情況具體分析了。
例如,signingConfig
就是這樣一個配置項。
可以設置android.buildTypes.release.signingConfig
讓所有release版本使用同一個SigningConfig,也可以單獨設置android.productFlavors.*.signingConfig
讓各release使用各自的SigningConfig。
6.4 源集合和依賴關係
和構建類型類似,產品flavor也可以通過他們自己的sourceSets影響最終的代碼和資源
在上面的例子中,創建了四個sourceSet:
- android.sourceSets.flavor1
位置src/flavor1/
- android.sourceSets.flavor2
位置src/flavor2/
- android.sourceSets.androidTestFlavor1
位置src/androidTestFlavor1/
- android.sourceSets.androidTestFlavor2
位置src/androidTestFlavor2/
這些sourceSet 都會用來創建apk,和android.sourceSets.main
以及構建類型的sourceSet一起。
下面是構建apk時,所有sourceSet的處理原則:
- 所有文件夾裏的源碼(src/*/java)都會被合併起來構建一個輸出。
- 多個Manifest文件會合併成一個。這樣使得flavor和構建類型一樣,可以有不同的組件和permission
- 所有資源的使用遵循優先級覆蓋,Product Flavor資源覆蓋main sourceSet資源,Build Type資源覆蓋Product Flavor資源
- 每個Build Variant會從資源中生成各自的R文件(或者其他生成的源碼)。各個Build Variant不會共享任何東西。
最後,和Build Type一樣,Product Flavor還可以有自己的依賴。例如,flavor包含了一個廣告版本和一個支付版本,那麼就會依賴廣告sdk,而其他版本不依賴。
dependencies {
flavor1Compile "..."
}
在這個特別的情況下,src/flavor1/AndroidManifest.xml
也許需要添加一個網絡權限
每個variant也會包含額外的sourceset:
- android.sourceSets.flavor1Debug
位置src/flavor1Debug/
- android.sourceSets.flavor1Release
位置src/flavor1Release/
- android.sourceSets.flavor2Debug
位置src/flavor2Debug/
- android.sourceSets.flavor2Release
位置src/flavor2Release/
這些sourceset比build type的sourceset有更高的優先級,允許variant級別的定製。
6.5 構建和任務
前面提到,每個Build Type有自己的assemble<name>
任務。但是Build Variant是Build Type 和 Product Flavor組合。
當使用Product Flavor的時候,更多的assemble-type任務會被創建出來,分別是:
- assemble\
允許直接構建一個Variant版本,例如assembleFlavor1Debug。 - assemble\
允許構建指定Build Type的所有APK,例如assembleDebug將會構建Flavor1Debug和Flavor2Debug兩個Variant版本。 - assemble\
允許構建指定flavor的所有APK,例如assembleFlavor1將會構建Flavor1Debug和Flavor1Release兩個Variant版本。
assemble
任務會構建所有可能的variant版本。
6.6 測試
測試多flavor的項目和簡單項目十分類似。
androidTest
sourceset被用來定義所有flavor的通用測試,同時,每個flavor也可以有各自的測試。
正如上面提到的,測試各flavor的sourceSet會被創建:
- android.sourceSets.androidTestFlavor1
位置src/androidTestFlavor1/
- android.sourceSets.androidTestFlavor2
位置src/androidTestFlavor2/
同樣,他們也可以有他們自己的依賴:
dependencies {
androidTestFlavor1Compile "..."
}
可以通過錨點任務deviceCheck
來運行測試,或者androidTest
任務(當使用flavor時,它作爲錨點任務)。
每個flavor有自己的任務運行測試:androidTest<VariantName>
。例如:
- androidTestFlavor1Debug
- androidTestFlavor2Debug
類似的,每個variant都有構建測試apk和安裝/卸載任務。
- assembleFlavor1Test
- installFlavor1Debug
- installFlavor1Test
- uninstallFlavor1Debug
- …
最後,生成的HTML報告支持按照flavor合併。
測試結果和報告位置如下,首先是每個flavor版本的,然後是合併的。
- build/androidTest-results/flavors/
- build/androidTest-results/all/
- build/reports/androidTests/flavors
- build/reports/androidTests/all/
改變任一個路徑,只會影響根目錄,仍然會爲每個flavor和合並後的結果創建子目錄。
6.7 多flaver維度的版本(Multi-flavor variants)
某些情況下,應用可能需要基於多個標準來創建多個版本。
例如,Google Play中multi-apk支持4個不同的過濾器。爲每一個過濾器而創建的apk要求使用多個Product Flavor維度。
假設有一個遊戲項目,有demo和付費版本,想要使用multi-apk中的ABI過濾器。由於要兼顧3種ABI和兩個版本,所以需要生成6個apk(沒有計算多個Build Type產生的版本)。
然而,付費版本的代碼對於所有三個ABI都是一樣,因此創建簡單的6個flavor不是一個好方法。
相反的,將flavor分爲兩個維度,並自動構建所有可能的組合variant。
這個功能通過Flavor Dimensions來實現。flavor都被分配到一個特定的維度
android {
...
flavorDimensions "abi", "version"
productFlavors {
freeapp {
flavorDimension "version"
...
}
x86 {
flavorDimension "abi"
...
}
}
}
在android.flavorDimensions
數組中定義可能的維度,並且每個flavor都指定一個維度。
根據已經劃分維度的flavor([freeapp, paidapp] 和 [x86, arm, mips]),和Build Type[debug, release],會創建以下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
中定義維度的順序非常重要。
每個variant配置由多個Product Flavor對象決定:
- android.defaultConfig
- One from the abi dimension
- One from the version dimension
維度的順序決定哪個flavor的配置會覆蓋另一個,這對資源來說很重要,高優先級flavor中的資源會替換低優先級的。flavor維度定義時高優先級在前。所以上面的例子中:
abi > version > defaultConfig
多維flavor項目也有額外的sourceset,和variant類似,但是沒有build type:
- android.sourceSets.x86Freeapp
Locationsrc/x86Freeapp/
- android.sourceSets.armPaidapp
Locationsrc/armPaidapp/
- etc…
這些sourceset允許在flavor-combination的級別進行定製。他們比基礎的flavor sourceset優先級高,但是比build type sourceset優先級低。
7 高級構建定製
7.1 構建選項
7.1.1 Java編譯選項
android {
compileOptions {
sourceCompatibility = "1.6"
targetCompatibility = "1.6"
}
}
默認值是1.6。影響所有編譯java源碼的任務。
7.1.2 aapt選項
android {
aaptOptions {
noCompress 'foo', 'bar'
ignoreAssetsPattern "!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"
}
}
影響所有使用aapt的任務。
7.1.3 dex選項
android {
dexOptions {
incremental false
preDexLibraries = false
jumboMode = false
javaMaxHeapSize "2048M"
}
}
影響所有使用dex的任務。
7.2 修改構建任務
基礎的Java項目有一套有限的任務共同工作來生成輸出。
classes
任務是一個編譯Java源碼的任務。很容易在build.gradle
文件的腳本中用classes
調用。這是project.tasks.classes
的縮寫。
在Android項目中,情況就有點複雜,因爲存在大量同樣的任務,他們的名字是基於Build Type 和 Product Flavor生成的。
爲了解決這個問題,android
有兩個屬性:
- applicationVariants (只適用於 app plugin)
- libraryVariants (只適用於 library plugin)
- testVariants (兩者都適用)
這三者分別返回一個ApplicationVariant
, LibraryVariant
,
和 TestVariant
對象的DomainObjectCollection。
要注意使用這些collection中的任一個都會觸發創建所有的任務。這意味着使用collection之後不應該修改配置。
DomainObjectCollection
可以直接訪問所有對象,或者通過過濾器篩選(更方便)。
android.applicationVariants.each { variant ->
....
}
所有三種variant共享下面這些屬性:
Property Name | Property Type | Description |
---|---|---|
name | String | variant的名字,必須是唯一的 |
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 | 合併asset的任務 |
processResources | ProcessAndroidResources | 處理並編譯資源文件的任務 |
generateBuildConfig | GenerateBuildConfig | 生成BuildConfig類的任務 |
javaCompile | JavaCompile | 編譯Java源代碼的任務 |
processJavaResources | Copy | 處理Java資源文件的任務 |
assemble | DefaultTask | variant的標誌任務assemble |
ApplicationVariant
擁有以下額外屬性:
Property Name | Property Type | Description |
---|---|---|
buildType | BuildType | variant的構建類型 |
productFlavors | List | Variant的ProductFlavor。一般不爲空但允許空值 |
mergedFlavor | ProductFlavor | android.defaultConfig和variant.productFlavors的合併 |
signingConfig | SigningConfig | variant使用的SigningConfig對象 |
isSigningReady | boolean | 如果是true則表明這個variant已經具備了簽名所需的所有信息 |
testVariant | BuildVariant | 將會測試這個variant的TestVariant |
dex | Dex | 將代碼打包成dex的任務,庫工程該屬性可以爲空 |
packageApplication | PackageApplication | 打包出最終apk的任務,庫工程該屬性可以爲空 |
zipAlign | ZipAlign | 對apk進行對齊(zipalign)的任務,庫工程或者apk無法簽名時,該屬性可以爲空 |
install | DefaultTask | 安裝apk的任務,可以爲空 |
uninstall | DefaultTask | 卸載任務 |
LibraryVariant
擁有以下額外屬性:
Property Name | Property Type | Description |
---|---|---|
buildType | BuildType | variant的構建類型 |
mergedFlavor | ProductFlavor | The defaultConfig values |
testVariant | BuildVariant | 用於測試這個variant的Variant |
packageLibrary | Zip | 打包成庫工程AAR文件的任務,非庫工程該屬性爲空 |
TestVariant
擁有以下額外屬性:
Property Name | Property Type | Description |
---|---|---|
buildType | BuildType | variant的構建類型 |
productFlavors | List | Variant的ProductFlavor。一般不爲空但允許空值 |
mergedFlavor | ProductFlavor | android.defaultConfig和variant.productFlavors的合併 |
signingConfig | SigningConfig | variant使用的SigningConfig對象 |
isSigningReady | boolean | 如果是true則表明這個variant已經具備了簽名所需的所有信息 |
testedVariant | BaseVariant | 被當前TestVariant測試的BaseVariant |
dex | Dex | 將代碼打包成dex的任務,庫工程該屬性可以爲空 |
packageApplication | PackageApplication | 打包出最終apk的任務,庫工程該屬性可以爲空 |
zipAlign | ZipAlign | 對apk進行對齊(zipalign)的任務,庫工程或者apk無法簽名時,該屬性可以爲空 |
install | DefaultTask | 安裝apk的任務,可以爲空 |
uninstall | DefaultTask | 卸載任務 |
connectedAndroidTest | DefaultTask | 在已連接的設備上運行android測試的任務 |
providerAndroidTest | DefaultTask | 使用擴展API運行android測試的任務 |
API for Android specific task types.
android特有任務的API:
- ProcessManifest
- File manifestOutputFile
- AidlCompile
- File sourceOutputDir
- RenderscriptCompile
- File sourceOutputDir
- File resOutputDir
- MergeResources
- File outputDir
- MergeAssets
- File outputDir
- ProcessAndroidResources
- File manifestFile
- File resDir
- File assetsDir
- File sourceOutputDir
- File textSymbolOutputDir
- File packageOutputFile
- File proguardOutputFile
- GenerateBuildConfig
- File sourceOutputDir
- Dex
- File outputFolder
- PackageApplication
- File resourceFile
- File dexFile
- File javaResourceDir
- File jniDir
- File outputFile
- 直接在Variant對象中使用“outputFile”可以改變最終的輸出文件。
- ZipAlign
- File inputFile
- File outputFile
- 直接在Variant對象中使用“outputFile”可以改變最終的輸出文件。
由於Gradle的工作方式和Android plugin的配置方式, 每個task類型的API是受限的。
首先,Gradle使得任務只能配置輸入輸出的路徑和一些可能使用的選項標識。因此,任務只定義一些輸入或者輸出。
其次,大多數任務的輸入都很複雜,一般都混合了sourceSet、Build Type和Product Flavor中的值。爲了保持構建文件的簡單,可讀,我們的目標是讓開發者通過略微改動DSL對象來修改構建過程,而不是深入到輸入文件和任務選項中去。
另外需要注意,除了ZipAlign這個任務類型,其它所有類型都要求設置私有數據來讓它們運行。這意味着不能手動創建這些任務的實例。
這些API也可能改變。大體來說,當前的API是圍繞着修改任務的輸入(可能的話)和輸出來添加額外的處理過程(必要的話)。歡迎反饋,特別是那些沒有預見到的問題。
對於Gradle任務(DefaultTask, JavaCompile, Copy, Zip),請參考Gradle文檔。
7.3 BuildType和Product Flavor屬性參考)
敬請期待。
對於Gradle任務(DefaultTask, JavaCompile, Copy, Zip),請參考Gradle文檔。
7.4 使用(JDK)1.7版本的sourceCompatibility)
使用Android KitKat(buildTools v19)就可以使用diamond operator,multi-catch,在switch中使用字符串,try with resource等等(jdk7中的新特性),要使用這些,需要修改你的構建文件如下:
android {
compileSdkVersion 19
buildToolsVersion "19.0.0"
defaultConfig {
minSdkVersion 7
targetSdkVersion 19
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
}
注意:你可以將minSdkVersion的值設置爲19之前的版本,只是你只能使用除了try with resources之外的語言特性。如果你想要使用try with resources特性,你就需要把minSdkVersion也設置爲19。
你同樣也需要確認Gradle使用1.7或者更高版本的JDK。(Android Gradle plugin也需要0.6.1或者更高的版本)