Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android Gradle插件用戶指南

Android Gradle插件用戶指南

編輯:關於Android編程

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.1SDK以及Buid Tools 19.0.0,某些功能可能需要更新的版本

3 基本項目

Gradle項目通過項目根目錄下的 build.gradle 文件來描述構建過程

3.1 簡單的構建文件

最簡單的Java項目構建文件 build.gradle

apply plugin: 'java'

這個腳本應用了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版本,和構建工具版本是必須的。在腳本中,對應的是compileSdkVersionbuildtoolsVersion屬性。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 codetest code,位於以下的目錄中:

src/main/
src/androidTest/

在這些目錄中,都存在目錄對應源碼組件

不管是Java還是Android插件,源碼目錄和資源目錄都如下 :

java/
resources/

對於Android插件,還有特有的文件和目錄

AndroidManifest.xml
res/
assets/
aidl/
rs/
jni/

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項目目錄下)中運行下面的任務可以查詢到高級別的任務:

gradle tasks

運行以下命令可以看到全部任務和任務依賴關系:

gradle tasks --all

Note: Gradle自動監視一個任務聲明的輸入輸出文件。再次執行構建任務時,如果文件沒有改變,Gradle會指明所有任務為UP-TO-DATE,意味著任務不需要執行。這樣的話,任務可以正確地互相依賴,而不不會導致非必須的構建操作

3.3.2 Java項目的任務

Java插件主要創建兩個任務,下面是這兩個錨點任務的依賴關系

assemble
jar
這個任務創建輸出 check
test
這個任務運行測試

jar任務直接或間接地依賴其他任務:例如classes任務將編譯Java源碼
testClasses任務用於編譯測試的,但是這個任務很少被調用,因為test任務依賴於它(就像依賴classes任務一樣)

通常來說,你只需要調用assemble或者check任務,而不需要調用其他任務。

你可以在Gradle Java插件文檔看到Java插件的全部任務和它們的描述

3.3.3 Android任務

Android插件使用同樣的約定來保持和其他插件的兼容,並且添加了額外的錨點任務:

assemble
這個任務組織項目的輸出

check
這個項目運行所有檢查

connectedCheck
運行檢查需要一個已連接的設備或者模擬器。並在所有已連接的設備上異步運行。

deviceCheck
通過APIs連接遠程設備並運行檢查。這通常在CI服務器上運行。

build
運行assemblecheck

clean
清理項目輸出

新的錨點任務是必須的,以保證在不需要設備連接的情況下能運行常規檢查。
需要注意的是,build任務並不依賴deviceCheck或者connectedCheck

一個Android項目至少有兩個輸出:debug APK 和 release APK。它們每一個都有自己的錨點任務來幫助它們完成獨立的構建:

assemble
assembleDebugassembleRelease

它們都依賴其它任務來完成構建一個apk所需要的多個步驟。assemble任務依賴這兩個任務,所以調用assemble會生成兩個APK。

Tip: Gradle支持在命令行中使用camel形式的任務名縮寫。
例如:
gradle aRgradle assembleRelease是一樣的,因為沒有別的任務名有同樣的縮寫

錨點任務check也有自己的依賴:

check
lint connectedCheck
connectedAndroidTestconnectedUiAutomatorTest (not implemented yet) deviceCheck
依賴於當其它插件實現測試擴展點時所創建的任務。

最終,插件會為所有構建類型(debug, release, test)創建install/uninstall任務,如果輸出文件可以安裝的話(必須簽名)。

3.4 基本的構建過程定制

Android插件提供了大量DSL來直接從構建系統中定制大多數事情。

3.4.1 Manifest屬性

通過DSL,可以配置以下manifest屬性:

minSdkVersiontargetSdkVersionversionCodeversionNameapplicationId (實際的packageName – 前往 ApplicationId versus PackageName 查看更多)Package Name for the test applicationInstrumentation 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構建類型:

將包名設置成.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,默認的路徑為:

src//

這意味這新的構建類型的名字不能是main或者androidTest(這是插件強制要求的),而構建類型的名稱必須是唯一的。

像其它sourceSet一樣,構建類型的sourceSet可以重新被定向:

android {
    sourceSets.jnidebug.setRoot('foo/jnidebug')
}

另外,每一個Build Type都會創建一個assemble任務。

在前面,assembleDebugassembleRelease已經提到過了,這就是它們的來源。當debug和release構建類型被預創建的時候,它們相關的任務就被自動創建了,比如assembleDebugassembleRelease

上面的build.gradle片段同樣會創建assembleJnidebug任務,assemble會像依賴assembleDebugassembleRelease任務一樣依賴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.txtproguard-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: dependenciesDSL元素是標准Gradle API的一部分,並不屬於android元素。

compile配置是用來編譯main應用的。任何添加到編譯路徑中的東西都會被打包到最終的apk文件中。
下面是其它一些在添加依賴時可能用到的配置:

compile: 主moduleandroidTestCompile: 測試moduledebugCompile: debug構建類型的編譯releaseCompile: release構建類型的編譯

因為構建一個apk必然有一個相關的構建類型,所以apk通常至少有兩個編譯配置:compileCompile
創建一個構建類型時會自動創建一個基於它名字的編譯配置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項目,:appAndroid項目將會使用它們輸出的jar文件。

然而,如果你想要共享使用了Android API或者Android資源文件的代碼(在庫項目中使用了Android API或Android資源文件),這些庫項目就不能是常規的Java項目,必須是Android庫項目。

4.3.1 創建一個庫項目

一個庫項目和常規的Android項目很相似,只有很少的區別。

因為構建庫項目和構建應用程序不一樣,所以使用不同的插件。構建庫項目的插件和構建應用程序的插件在內部共享大部分的代碼,並且它們都是由com.android.tools.build.gradlejar庫提供。

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名稱,releasedebug這兩個名稱只有在沒有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自動化測試。

測試應用的節點是自動生成的,但是你也可以創建一個src/androidTest/AndroidManifest.xml ,並在這個manifest文件中添加其他組件。

下面是一些測試應用可以配置的值:

testPackageNametestInstrumentationRunnertestHandleProfilingtestFunctionalTest

正如前面所看到的,這些在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將會執行下面內容:

確認應用和測試應用都被構建(依賴於assembleDebugassembleTest)安裝這兩個應用運行這些測試卸載這兩個應用.

如果有多於一個連接設備,那麼所有測試都會同時運行在所有連接設備上。如果其中一個測試失敗,不管是哪一個設備,這個構建就失敗。

所有測試結果都被保存為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//

這個路徑也可以通過以下方式自定義:

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,分別是 flavor1flavor2
注意: flavor的名字不能喝已有的構建類型(Build Type)名字沖突,或者和androidTest這個sourceSet的名字沖突。

6.2 構建類型+產品定制=變種版本(Build Type + Product Flavor = Build Variant)

前面已經提到,每一個構建類型都會生成一個apk。忘了的話,請看3.4.2

Product Flavors 也會做同樣的事情,實際上,項目輸出來自所有可能的,Build TypesProduct Flavors的組合,如果有Product Flavors的話。

每種Build TypesProduct Flavors的組合就是一個Build Variant

例如,默認的debugrelease這兩個Build Types,和上面創建的兩個flavor會生成4個Build Variants

Flavor1 - debugFlavor1 - releaseFlavor2 - debugFlavor2 - 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.flavor1minSdkVersion: 8versionCode: 20 flavor2
packageName: com.example.flavor2minSdkVersion: 14versionCode: 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任務。但是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。例如:

androidTestFlavor1DebugandroidTestFlavor2Debug

類似的,每個variant都有構建測試apk和安裝/卸載任務。

assembleFlavor1TestinstallFlavor1DebuginstallFlavor1TestuninstallFlavor1Debug…

最後,生成的HTML報告支持按照flavor合並。
測試結果和報告位置如下,首先是每個flavor版本的,然後是合並的。

build/androidTest-results/flavors/build/androidTest-results/all/build/reports/androidTests/flavorsbuild/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-debugx86-freeapp-releasearm-freeapp-debugarm-freeapp-releasemips-freeapp-debugmips-freeapp-releasex86-paidapp-debugx86-paidapp-releasearm-paidapp-debugarm-paidapp-releasemips-paidapp-debugmips-paidapp-release

android.flavorDimensions中定義維度的順序非常重要。

每個variant配置由多個Product Flavor對象決定:

android.defaultConfigOne from the abi dimensionOne from the version dimension

維度的順序決定哪個flavor的配置會覆蓋另一個,這對資源來說很重要,高優先級flavor中的資源會替換低優先級的。flavor維度定義時高優先級在前。所以上面的例子中:

abi > version > defaultConfig

多維flavor項目也有額外的sourceset,和variant類似,但是沒有build type:

android.sourceSets.x86Freeapp
Location src/x86Freeapp/android.sourceSets.armPaidapp
Location src/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:.*:
_*:!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 sourceOutputDirFile resOutputDir MergeResources
File outputDir MergeAssets
File outputDir ProcessAndroidResources
File manifestFileFile resDirFile assetsDirFile sourceOutputDirFile textSymbolOutputDirFile packageOutputFileFile proguardOutputFile GenerateBuildConfig
File sourceOutputDir Dex
File outputFolder PackageApplication
File resourceFileFile dexFileFile javaResourceDirFile jniDirFile outputFile
直接在Variant對象中使用“outputFile”可以改變最終的輸出文件。 ZipAlign
File inputFileFile outputFile
直接在Variant對象中使用“outputFile”可以改變最終的輸出文件。

由於Gradle的工作方式和Android plugin的配置方式, 每個task類型的API是受限的。
首先,Gradle使得任務只能配置輸入輸出的路徑和一些可能使用的選項標識。因此,任務只定義一些輸入或者輸出。

其次,大多數任務的輸入都很復雜,一般都混合了sourceSet、Build Type和Product Flavor中的值。為了保持構建文件的簡單,可讀,我們的目標是讓開發者通過略微改動DSL對象來修改構建過程,而不是深入到輸入文件和任務選項中去。

另外需要注意,除了ZipAlign這個任務類型,其它所有類型都要求設置私有數據來讓它們運行。這意味著不能手動創建這些任務的實例。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved