应用Gradle构建系统协助Android开发

working with Gradle for Android Development

这是一个官方用户指南的精简版本

[TOC]

Introduction | 介绍

Gradle是一套先进的编译体系以及工具包,可以通过各种插件创建自定义编译构建逻辑。

Goals of Gradle | 目标

  • 简化代码和资源的重用
  • 简化为一个项目创建多个变种版本的工作,包括支持multi-apk打包,和维护一个应用的多个不同风味的发行版(比如:pad,lite,free,full,main等)
  • 简化自定义和拓展编译构建过程的工作
  • 优良的IDE整合支持

Why Gradle? | 何得何能?

  • 使用领域特定语言 (DSL)来表述和操控编译构建逻辑
  • 编译脚本文件基于 Groovy,并且允许采用DSL和代码混合使用的方式来操控DSL元素,从而自定义构建过程
  • 可以选择采用Maven或Ivy来实现内置的工程包依赖管理系统
  • 相当灵活,允许采用最佳的实践方案,但不会强制拘束任何实践细节
  • 插件可以暴露一部分DSL元素和API一共编译脚本文件使用
  • 提供了一套优良的工具API以供IDE集成使用

Requirements | 系统要求

  • Gradle 1.10 or 1.11 or 1.12 with the plugin 0.11.1
  • SDK with Build Tools 19.0.0. Some features may require a more recent version.

Basic Project | 基础工程介绍

Gradle工程在其工程根目录有一个名为build.gradle的文件,用来描述该工程的编译构建过程

Simple build files | 简易构建脚本

下边是一个最简单的android工程的build.gradle构建脚本

buildscript {
repositories {
mavenCentral()
}

dependencies {
classpath 'com.android.tools.build:gradle:0.11.1'
}
}

apply plugin: 'android'

android {
compileSdkVersion 19
buildToolsVersion "19.0.0"
}

下面是这个android构建脚本的3个基本元素

apply plugin

插件为一个工程的构建和测试提供了一切所需,对于android工程,你需要指定应用android插件,如果指定应用java插件的话就会出编译问题;

  • apply plugin: 'java'
    Gradle内建该插件,适用于构建和测试java工程的一切所需
  • apply plugin: 'android'
    Gradle 从v0.11.1起内建该插件,类似java插件,提供了构建和测试android工程的一切所需

buildscript

buildscript { ... }用于配置构建脚本如何驱动构建过程。
上面的例子中,他描述需要使用Maven Central repository,并且有一个Maven artifact的classpath依赖关系,这神器是Gradle v0.11.1中包含安卓插件的一个库;
注意:这些配置仅仅影响Gradle构建系统本身的执行,跟具体的project没有关系,具体的project需要具体定义他自己的repositories和dependencies,下面将会介绍这些内容。

android

android { ... }用于所有与Android构建相关的参数,这是Android DSL元素的切入点;
默认情况下只有编译目标和编译版本是必须的,可以通过compileSdkVersionbuildtoolsVersion为其赋值。
这里的编译目标等同于先前老编译系统的project.properties文件中target属性;

注意: 仍需在local.properties文件中定义sdk.dir属性以指明SDK的位置,或者设置一个名为ANDROID_HOME的环境变量,两种方法效果等同,酌情使用。

Project Structure | 工程结构

基础构建脚本指明了一个默认的工程目录结构,Gradle支持约定优于配置的观点,在可能的情况下提供了合理的默认选项值

基础工程有两个称之为“代码集”的组件。一个用来存放主代码,另一个用来存放测试用例;如下所示:

  • src/main/
  • src/androidTest/

目录下是各自对应的代码组件。对于java和android工程来讲,代码包括java源码和资源文件:

  • java/
  • resources/

余下这些是目录Android工程所特有的:

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

注意: src/androidTest/AndroidManifest.xml无需干预,会自动创建。

Configuring the Structure | 配置工程目录

如果默认配置不能满足需求,也可以自定义配置选项,根据Gradle的文档,可参照如下方式为java工程重新配置sourceSets

sourceSets {
main {
java {
srcDir 'src/java'
}
resources {
srcDir 'src/resources'
}
}
}

注意: 实际上srcDir会被添加到已有的源码目录列表中(虽然Gradle文档为提及,但确是如此)

如需使用新的源码目录彻底替换原有的srcDirs,需要使用一个目录列表,如下所示为另外一种赋值方式:

sourceSets {
main.java.srcDirs = ['src/java']
main.resources.srcDirs = ['src/resources']
}

Gradle java plugin官方文档参见The Java Plugin

对应的,android工程有类似的语法,由于使用使用了他自己的sourceSets,所以要定义在android组件中。
下面是实现了android老工程结构的一个例子:

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')
}
}

注意: 由于老的android工程结构把所有源码文件(包括java, aidl, renderscript, and java resources)都放在一个目录中了,所以我们需要把所有新定义的sourceSets统统映射到src目录下。

注意: setRoot()会将整个sourceSet重新映射到一个新的目录,包括他的所有子目录,上边例子把src/androidTest/*映射到了tests/*

该定制脚本尽适用于Android而不适用与java。

该脚本可以应用于老工程的迁移。

Build Tasks | 构建任务

General Tasks | 常规任务

应用一个插件可以自动化的创建并运行一系列构建任务,java和android工程亦如此;
任务约定如下所示:

  • assemble
    编译产出的中间文件的打包组建任务
  • check
    执行一切检查任务
  • build
    执行assemblecheck
  • clean
    清除编译产出的文件

实际上默认情况下assemble, checkbuild 并没有实际执行,他提供了一个类似回调接口的机制,以供配置执行各种插件的各种任务。

它提供了一个执行全局任务的机会,无论构建任何类型的任何project统统都会执行的任务。
举个例子,比如我们希望对全局都使用findbugs插件,就可以在此配置,执行check任务时执行;

命令行下查看任务:

  • gradle tasks
    查看上层运行的tasks。
  • gradle tasks --all
    查看包括低层依赖在内的所有可执行的task:

注意: Gradle会自动监听某个任务所声明的输入和输出变化。
连续执行两次build操作会提示所有任务都UP-TO-DATE,这意味着没有新工作需要处理,说明工程依赖关系是正确的。

Java project tasks | java工程任务

java插件主要创建两个任务,这依赖于主锚任务

  • assemble
  • jar
    这个任务生成产出
  • check
  • test
    这个任务执行测试

jar任务本身是直接或间接的依赖于其他任务的:其中classes 任务是一个用来编译java源码的实例.
testClasses任务用来编译测试用例,由于test任务依赖于他所以很少被直接调用,(classes任务也是如此)

通常,我们只需要直接调用assemblecheck任务就好了,其他任务可以忽略。

完整任务表述参见官方文档The Java Plugin

Android tasks | Android任务

android插件兼容并使用同其他插件相同的约定,并且额外新增了几个锚点任务:

  • assemble
    组装各种产出文件的任务。
  • check
    执行各种检查的任务
  • connectedCheck
    并行执行检查设备链接的任务,包括模拟器和真机。
  • deviceCheck
    执行使用APIs链接远程设备的检查,用于持续集成。
  • build
    执行组装和检查任务。
  • clean
    执行清理产出的任务。

新创建的锚点任务必须支持没有可用链接设备的情况。
需要注意的是,build任务并不依赖于deviceCheckconnectedCheck

对于Android工程而言,至少会有两个产出:一个排错版APK和一个发行版APK,对于其中的每一个都会有对应的锚点任务以便分别构建:

  • assemble
  • assembleDebug
  • assembleRelease

上述两个的构建过程都依赖于多个其他任务步骤的执行,assemble任务又依赖于他们两个,所以执行结果会构建产生两个APK文件。

上述两个的构建过程都依赖于多个其他任务步骤的执行,assemble任务又依赖于他们两个,所以执行结果会构建产生两个APK文件。

小贴士 Gradle支持驼峰式写法的缩写,举个例子:在没有其他相同匹配的情况下gradle aR等同于 gradle assembleRelease

检查锚点任务有他们自己的依赖关系:
小贴士 Gradle支持驼峰式写法的缩写,举个例子:在没有其他相同匹配的情况下gradle aR等同于 gradle assembleRelease

检查锚点任务有他们自己的依赖关系:

  • check
  • lint
  • connectedCheck
  • connectedAndroidTest
  • connectedUiAutomatorTest (not implemented yet)
  • deviceCheck
  • This depends on tasks created when other plugins implement test extension points.

最终插件会针对所有构建类型 (debug, release, test)创建安装/卸载任务,当然,只有经过签名之后才能被安装。

Basic Build Customization | 自定义构建过程基础

Android插件提供了广范的DSL元素,用于直接定制构建系统中的大部分事物。

Manifest entries | Manifest选项

通过DSL组件,可以配置以下manifest选项

  • minSdkVersion
  • targetSdkVersion
  • versionCode
  • versionName
  • applicationId (the effective packageName — see ApplicationId versus PackageName for more information)
  • Package Name for the test application
  • Instrumentation test runner

Example:

android {
compileSdkVersion 19
buildToolsVersion "19.0.0"

defaultConfig {
versionCode 12
versionName "2.0"
minSdkVersion 16
targetSdkVersion 16
}
}

这些配置选项都在android元素中包含的defaultConfig元素中定义。

先前的Android插件使用packageName去配置manifest中的’packageName’属性。
但是在Gragle v0.11.0之后需要使用applicationId来配置manifest中的’packageName’属性。
这是为了消除apk包名和java报名之间的歧义。

在构建脚本中配置这些属性的一个强大之处在于,他们可以是动态配置的;
如下所示,他可以给版本名赋予一个定义在任意文件中的值,或者直接通过一段自定义的逻辑代码来生成值:

def computeVersionName() {
...
}

android {
compileSdkVersion 19
buildToolsVersion "19.0.0"

defaultConfig {
versionCode 12
versionName computeVersionName()
minSdkVersion 16
targetSdkVersion 16
}
}

注意: 方法名不要和给定作用范围内已有的getter名字冲突,举个例子,在defaultConfig { ...}的实例中调用getVersionName()就会自动掉defaultConfig.getVersionName()的getter方法,而不是自定义方法。

如果某一属性没有通过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。

Build Types | 构建类型

默认情况下,android插件会自动为工程配置两个构建类型,一个debug版,一个release版。
两种构建类型的区别在于debug版可以在安全设备(非开发设备)上调试,另外他们的签名方式也不同。

debug版的会自动生成一个免密码的key用来签名(这是为了防止构建过程中出现输入密码的提示框)。release版在构建时不会被签名,他需要在稍后再签名。

This configuration is done through an object called a BuildType. By default, 2 instances are created, a debug and a release one.
这是通过一个叫BuildType的东西配置的,默认情况下会有两个实例被创建,一个debug和一个release

android插件允许自定义这两个构建类型,并且可以创建其他构建类型,这是在buildTypes这个DSL容器中完成的:

android {
buildTypes {
debug {
applicationIdSuffix ".debug"
}

jnidebug.initWith(buildTypes.debug)
jnidebug {
packageNameSuffix ".jnidebug"
jniDebuggable true
}
}
}

上面的代码片段实现了以下内容:

  • 配置默认的debug构建类型:
  • 将其包名设置为.debug,以便在一个设备上同时安装正式版和测试版
  • 创建了一个名为jnidebug的构建类型,并将其初始化为debug的副本。
  • 继续配置jnidebug,使调试JNI组件有效,并且为定义了一个不同的apk后缀。

创建一个新的构建类型就同在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支持创建专有其的源码和资源。
对于每个Build Type都会创建一个新的代码集,其默认位置在:
src//
这意味着Build Type的名字不能是mainandroidTest(这是有android插件强制决定的),并且要保持唯一。

同其他代码集一样,构建类型的代码集目录位置也可以被自定义。

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

此外对于每种构建类型,都会相应的创建一个名为assemble任务。

上面提到的assembleDebugassembleRelease任务是从哪儿来的呢,他是在debugrelease编译类型即将创建是自动生成的。

上述build.gradle中的片段将会生成一个assembleJnidebug任务,assemble会以assembleDebugassembleRelease 相同的方式依赖于他。

小贴士: 别忘了,可以用gradle aJ来调用assembleJnidebug 任务。

可能是应用场景:

  • 发行版中没有,仅在调试模式下存在的权限
  • 为了调试而自定义的逻辑实现
  • 调试模式下的特有资源(比如绑定到签名证书的资源)

构建类型的源码/资源将以下列方式使用:

  • manifest合并到app manifest
  • 只是存在与另一个代码目录的代码
  • 一些凌驾于主要资源的资源,覆盖一些原先的值

Signing Configurations | 配置签名

对一个应用签名的要求如下:

  • A keystore
  • A keystore password
  • A key alias name
  • A key password
  • The store type

签名的配置(SigningConfig)包括签名文件目录位置以及名称,两个密码

默认的,debug配置会自动设置一个已知name和密码的keystore。
他会在自动创建在$HOME/.android/debug.keystore目录中。

可以通过signingConfigs这个DSL容器配置或自定义默认的debug key:

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
}
}
}

上面的代码将keystore的位置定义为工程的跟目录,这将自动影响所有使用他的构建类型,上例中他将影响debug构建类型。

另外还创建了一个新的签名配置,并且将他应用到了一个新的构建类型上。

注意: 只有debug keystore位于默认位置时他才会被自动创建,改变位置之后就不会自动创建了,只是创建另外一个签名配置兵使用另外的名字的话还是会自动创建的,换言之,他关心的只是keystore的所在位置,而不是配置和名字。

注意: keystores的位置通常是工程的根目录,也可以是一个绝对路径,但是不推荐这么做(除非是用于调试且自动创建的)。

注意: 如果要把这些文件加入版本控制管理,有可能不希望密码被明文写在文件中,那么Stack Overflow中的这篇帖子指明了如何从命令行或环境变量读取密码值的方法。
我会在稍后更新这部分内容。

Running ProGuard | 执行混淆

ProGuard v4.10 一插件的形式被支持。已经自动应用了混淆插件,并且在构建类型中通过设置minifyEnabled属性,自动创建任务并开启

android {
buildTypes {
release {
minifyEnabled true
proguardFile getDefaultProguardFile('proguard-android.txt')
}
}

productFlavors {
flavor1 {
}
flavor2 {
proguardFile 'some-other-rules.txt'
}
}
}

产品变种会应用通过在所有规则文件中声明的构建类型和产品风味中的配置。

以下是两个规则文件:

  • proguard-android.txt
  • proguard-android-optimize.txt

他俩位于SDK中,可以通过getDefaultProguardFile()方法得到他俩的完整位置,他俩除了是否应用优化之外完全相同。

Shrinking Resources | 压缩资源

可以在编译时自动删除没有使用的资源,详情参见文档压缩资源

Dependencies, Android Libraries and Multi-project setup | 依赖关系,Android库和Multi-project配置

Gradle工程有可能是依赖于其他组件的,这些组件可以是一个外部的二进制包,或者是另外一个Gradle工程

Dependencies on binary packages | 依赖于二进制包

Local packages | 本地包

可以通过配置compile属性来配置一个外部jar包。

dependencies {
compile files('libs/foo.jar')
}

android {
...
}

注意: dependenciesDSL元素是Gradle标准API的一部分,而并非属于android元素。

该编译配置将会用在编译主应用时,他定义的所有东西都会加入编译的classpath,并且会被打进最终的包里。
其他添加依赖关系的方式有如下几种:

  • compile: main application
  • androidTestCompile: test application
  • debugCompile: debug Build Type
  • releaseCompile: release Build Type.

以为依赖库本身并不能构建出一个APK文件来,所以他们不支持划分编译类型,APK的编译类型永远只有这两大类: compile and Compile
创建一个新的构建类型将会基于构建类型名称自动的生成一个新的构建配置。

这在下边的情况下是很有用的,如调试版本需要用到一个特殊的库(比如崩溃报告),而发行版并不需要,或者他们依赖于同一个库的不同版本。

Remote artifacts | 远端神器

Gradle支持采用Maven和Ivy自动拉去远端神器(就是指具体的依赖包)。

首先,依赖包的库必须在他们的可用列表里,然后,需要按照Maven或Ivy的方式把他们声明出来。

repositories {
mavenCentral()
}

dependencies {
compile 'com.google.guava:guava:11.0.2'
}

android {
...
}

注意: mavenCentral()是指定中心仓库URL的快捷方式,Gradle同时支持远端和本地仓库。
注意: Gradle遵循依赖关系的传递性,这意味着他可以管理依赖的依赖。

更多依赖关系配置的咨询详见Gradle用户指南DSL文档

Multi project setup | 多工程配置

采用多工程配置可以shi使某Gradle工程依赖于另外的Gradle工程。如下所示:

MyProject/
+ app/
+ libraries/
+ lib1/
+ lib2/

我们可以按如下所示标记这三个工程,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的内容非常简单,我们可以按如下所示定义这些目录实际上是一些Gradle工程:

include ':app', ':libraries:lib1', ':libraries:lib2'

app工程可能是依赖于其他这些库工程的,可以俺如下所示定义依赖关系:

dependencies {
compile project(':libraries:lib1')
}

More general information about multi-project setup here.
更多详情参见多工程配置

Library projects | 库工程

在上面的多工程配置中:libraries:lib1:libraries:lib2可以是java工程,而Android工程:appshi ji shang 实际上使用的是他们生成的jar。
然而,如果你要共享的代码使用了Android APIs或者使用了Android风格的资源文件,那么普通的库就不能胜任了,这就需要采用Android库工程。

Creating a Library Project | 创建库工程

Android库工程和普通工程非常相似,只有一点区别。
由于Android库工程和普通工程还是有些差别,所以这就需要采用另外一个Gradle插件了。两个插件的内部实现共享了大部分代码,并且都是由同一个名为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
}

这样就创建了一个使用API15来编译的库工程。代码集和依赖关系的自定义配置都跟主工程一样。

Differences between a Project and a Library Project | 普通工程和库工程的区别

库工程主要产出一个.aar包(标准Android archive)他整合了所有源码(比如jar文件和原生的.so文件)和资源文件(比如manifest,res,assets)。
库工程也可以生成一个测试APK,用于从应用独立测试该库。

库工程同样有锚点任务(assembleDebug, assembleRelease),这使得他们在命令行下跟普通工程是一样的,

其他方面,库工程的行为和普通应用工程是一样的,同样支持构建类型和产品风味,并且可以生成不止一个版本的aar。
需要注意的是普通应用工程的构建类型的大部分配置不会直接应用于库工程,然而却可以在测试或被用于一个工程,使用不同的代码集。

Referencing a Library | 引用库

库工程引用其他库的方式与普通工程相同:

dependencies {
compile project(':libraries:lib1')
compile project(':libraries:lib2')
}

**注意: ** 如有一个以上的库工程,他们的优先级顺序是很重要的,这跟之前的构建系统中的project.properties文件中的依赖关系顺序一样重要。

Library Publication | 发布库

一般情况下,一个库只能发布他的release变种。这个变种要能在任何工程中引用,无论他们用什么变种构建。这是Gradle的一个临时性的限制,我们正在努力的解决这个问题。

可以这样控制哪个变种将被发布

android {
defaultPublishConfig "debug"
}

注意发布配置要使用变种的全名,Releasedebug仅能在没有任何产品风味的情况下使用,如果想要改变默认的发布风味,可以这么写:

android {
defaultPublishConfig "flavor1Debug"
}

为库工程发布所有变种也是可行的,我们计划允许像使用工程间依赖相同的方式(如上所示),不过,由于Gradle的限制,目前还做不到这一点(我们正朝这个方向努力)。
发布所有变种的功能是默认关闭的,可以这样打开:

android {
publishNonDefault true
}

要意识到一个很重要的事情就是,发布多个变种意味着发布很多的aar文件,而不是一个aar文件中有含有很多个单独的变种。
发布一个变种意味着让这个aar可以作为一个Gradle工程的神器产出。他可以被发布在maven仓库里,也可以让其他工程直接以他做为依赖。

Gradle有一个默认神器的概念,下面是用到他时的一种写法:

compile project(':libraries:lib2')

建立一个其他神器的依赖关系,可以指定具体要用哪个:

dependencies {
flavor1Compile project(path: ':lib1', configuration: 'flavor1Release')
flavor2Compile project(path: ':lib1', configuration: 'flavor2Release')
}

重要: 注意发布出来的配置是个全功能变种,包括了构建类型,还有他们需要的引用之类的。
重要: 如果开启了非默认的发布,maven的发布插件将会发布那些附加变种和额外的包(包含classifier)。这就意味着不能真正兼容于maven库的发布。你需要单独在仓库中发布一个变种,或者将所有有依赖关系的工程都开起发布。

Testing | 测试

// TODO:

Unit Testing | 单元测试

// TODO:

Basics and Configuration | 基础和配置

// TODO:

Running tests | 执行测试

// TODO:

Testing Android Libraries | 测试Android库

// TODO:

Test reports | 测试报告

// TODO:

Single projects | 单个工程

// TODO:

Multi-projects reports | 多个工程

// TODO:

Lint support | Lint支持

// TODO:

Build Variants | 构建变种版本

新构建系统的目标之一是具有为同一个应用构建多个不同的变种版本的能力。

下面是几个重要的应用场景:

  1. 同一个应用的不同版本,例如一个免费/试用版本和一个付费专业版应用。
  2. 同一个应用,需要打多个不同的包,支持Google Play商店的multi-apk功能,详情参见Multiple APK Support
  3. 1和2综合起来的情况。

目标是使用同一个工程具备生成不同版本APK的能力,而非采用一个库工程外加几个普通应用工程的方式。

Product flavors | 产品风味

产品风味定义了工程如何构建出一个定制版本,一个单独的工程可以生成多种不同版本的应用程序。

这个概念是为了帮助那些存在微小差异的产品需求而设计的,如果要问他们还算是同一个应用程序吗,那么答案是肯定的,大家可能因此而走过依赖库工程的老路。

通过productFlavorsDSL容器来声明产品风味

android {
....

productFlavors {
flavor1 {
...
}

flavor2 {
...
}
}
}

上面创建了两个产品风味,分别是flavor1flavor2
注意: 产品风味名不能和构建类型以及androidTest代码集重名。

Build Type + Product Flavor = Build Variant | 构建类型 + 产品风味 = 变种版本

正如前所示,每个构建类型生成一个新的APK。

产品风味也一样:如果适用的话,工程将产生构建类型和产品风味的所有可能的组合。

每个(构建类型,产品风味)的组合被称为一个构建变种。

如,算上默认的debugrelease两种构建类型和上面例子中的产品风味,将生成四个构建变种:

  • Flavor1 – debug
  • Flavor1 – release
  • Flavor2 – debug
  • Flavor2 – release

如果工程没有配置产品风味,他依然存在构建变种,只不过用了一个default的产品风味/配置,省略了名字,导致变种列表跟构建类型列表一致。

Product Flavor Configuration | 配置产品风味

每个风味都配置在一个大括号中:

android {
...

defaultConfig {
minSdkVersion 8
versionCode 10
}

productFlavors {
flavor1 {
packageName "com.example.flavor1"
versionCode 20
}

flavor2 {
packageName "com.example.flavor2"
minSdkVersion 14
}
}
}

注意android.productFlavors.*对象为ProductFlavor类型,与android.defaultConfig相同,这就意味着,他们内部有相同的配置项目。

defaultConfig提供了所以风味的默认配置,每个风味都可以覆盖重写任何配置选项,如下所示:

  • flavor1
  • packageName: com.example.flavor1
  • minSdkVersion: 8
  • versionCode: 20
  • flavor2
  • packageName: com.example.flavor2
  • minSdkVersion: 14
  • versionCode: 10

通常构建类型中的设置优先级较高,会重写其他配置项。例如,构建类型的packageNameSuffix会加在产品风味的报名之后。

下列设置都可以在构建类型和产品风味中生效。个别需要视情况下而定,

例如,签名设置就是其中之一。
这使得签名既可以让所有发行版都共享android.buildTypes.release.signingConfig中相同的SigningConfig,又可以通过android.productFlavors.*.signingConfig为个别产品风味的发行版配置特有的SigningConfig

Sourcesets and Dependencies | 代码集与依赖关系

类似与构建类型,产品风味也可以在他们自己的源码集中提价代码。

上面的例子产生了四个代码集:

  • android.sourceSets.flavor1
    Location src/flavor1/
  • android.sourceSets.flavor2
    Location src/flavor2/
  • android.sourceSets.androidTestFlavor1
    Location src/androidTestFlavor1/
  • android.sourceSets.androidTestFlavor2
    Location src/androidTestFlavor2/

这些代码集可以佐以主代码集android.sourceSets.main和构建类型代码集来构建APK。

用于构建APK的所有代码集的处理规则如下:

  • 在多个目录下的所有代码(src/*/java)一同被用于产生一个构建。
  • 类似与构建类型,所有需要的manifest文件被合并成一个文件,这使得不同风味的产品可以有不同的manifest文件包含不同的组件和权限。
  • 一切需要的资源(Android资源和assets)按后面的优先级规则重写覆盖,构建类型代码覆盖产品风味代码覆盖主代码。
  • 每个变种都会从资源文件中生成他自己的独立的R文件(或其他自动生成的文件),没有任何东西是共享的。

最后,类似与构建类型,产品风味可以有自己的依赖关系,例如,产品风味被用与生成一个有广告的和一个付费的app,那么其中一个可能会依赖一个广告SDK,而另一个则不会。

dependencies {
flavor1Compile "..."
}

这种情况下,src/flavor1/AndroidManifest.xml可能需要声明一个需要网络的权限。

每个变种将会创建额外的代码集:

  • android.sourceSets.flavor1Debug
    Location src/flavor1Debug/
  • android.sourceSets.flavor1Release
    Location src/flavor1Release/
  • android.sourceSets.flavor2Debug
    Location src/flavor2Debug/
  • android.sourceSets.flavor2Release
    Location src/flavor2Release/

这些代码集的优先级高于构建类型代码集,并且在变种岑面上允许做一些自定义的工作。

Building and Tasks | 构建与任务

之前我们看到根据每个构建类型会创建名外assemble的任务,而构建变种则需要组合构建类型和产品风味。

当引用了产品风味后,更多的构建任务会被创建,如下:

  1. assemble
    允许直接构建一个变种,例如assembleFlavor1Debug
  2. assemble
    允许构建给定类型的所有APK,例如assembleDebug会生成Flavor1DebugFlavor2Debug两个变种。
  3. assemble
    允许构建给定风味的APK,例如assembleFlavor1会生成Flavor1DebugFlavor1Release两个变种。

assemble任务会构建所有可能的产品变种。

Testing | 测试

// TODO:

Multi-flavor variants | 多种风味的变种版本

某些情况下,可能会需要使用相同的产品为不同环境构建不同APK。
例如在Google Play中多apk支持4中不同的过滤项。为每个不同的选项分别生成不同的APK,可以避免在一个APK中存在多份尺度资源的情况。

试想一下,例如一个游戏,需要分试玩版和免费版,又希望采用multi-apk支持ABI过滤出不同的安装包,那么这个应用就需要生成3个ABI和两个风味中共6个APK(这还不算构建类型)。
然而付费版本的几个ABI版本代码完全一样,所以简单的创建6个产品风味没有意义。
取而代之的是Gradle可以自动将所有可能的尺度组合都构建出来。

下面是使用风味尺度的例子,将风味应用于尺度

android {
...

flavorDimensions "abi", "version"

productFlavors {
freeapp {
flavorDimension "version"
...
}

x86 {
flavorDimension "abi"
...
}
}
}

android.flavorDimensions定义了一列可能的尺度以及其顺序,每个产品口味都会按尺度分配。

根据后面的口味尺度[freeapp, paidapp]和[x86, arm, mips]和[debug, release]编译类型,可以为一个风味构建出以下不同的尺度版本。

  • 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中定义的顺序是非常重要的。

每个变种均配置为几个产品风味对象:

  • android.defaultConfig
  • One from the abi dimension
  • One from the version dimension

尺度的顺序决定了覆盖关系,这对资源文件来说的很重要的,高优先级风味版本中的值会覆盖地低先级版本的。
定义越靠前的优先级越高,在上面的例子中优先级关系为:

abi > version > defaultConfig

多风味的工程可以有附加的代码集,类似于变种代码集,但是没有构建类型:

  • android.sourceSets.x86Freeapp
    Location src/x86Freeapp/
  • android.sourceSets.armPaidapp
    Location src/armPaidapp/
  • etc…

这允许了定制风味组合的优先级层次。他们的优先级高于基本风味的代码集,却低于构建类型的代码集。

Advanced Build Customization | 高级自定义构建

// TODO:

Build options | 构建选项

// TODO:

Java Compilation options | java编译选项

// TODO:

aapt options | aapt选项

// TODO:

dex options | dex选项

// TODO:

Manipulating tasks | 操控任务

// TODO:

BuildType and Product Flavor property reference | 构建类型和产品风味的参数参考

// TODO:

Using sourceCompatibility 1.7 | 使源码兼容java 1.7

// TODO:


发表评论

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据