正文  软件开发 > 编程综合 >

Gradle脚本指南

便于复用代码和资源 便于创建应用中的多种变量, 用于多渠道分发apk 便于配置, 扩展和自定义构建过程 良好的IDE整合性 为什么选用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"
}

其中有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')
 }
}

注意:由于老的项目结构将所有的源代码文件(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
      该任务创建输出
  • check
    • test
      该任务运行测试

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

他们都依赖于其他任务. assemble 任务同时依赖于这两个任务, 因此运行该指令会构建两种APK.

提示:Gradle支持驼峰式的任务名称简写, 例如:

gradle aR

与下面的命令相同

gradle assembleRelease

只要没有其他任务匹配’aR’即可

check锚点任务有自己的依赖:

  • check
    • lint
  • connectedCheck
    • connectedAndroidTest
  • deviceCheck
    • 依赖于其他实现测试扩展点的插件

最后, 插件会创建任务来安装和卸载所有构建类型(debug, release, test), 例如

  • installDebug
  • installRelease
  • uninstallAll
    • uninstallDebug
    • uninstallRelease
    • uninstallDebugAndroidTest

基本构建自定义

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

在构建文件中使用这些manifest属性的意义在于, 这些值可以动态设置. 例如, 你可以使用自定义逻辑从一个文件中读取版本名称:

def computeVersionName(){
 ...
}

android {
 compileSdkVersion 23
 buildToolsVersion "23.0.1"

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

注意:不要使用与作用域中已存在的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
 }
 }
}

上面的代码实现了如下功能:

  • 配置了默认的 debug 构建类型:
    • 设置包名为 <app applicationId>.debug 以便在同一个设备上同时安装debug和release的apk
  • 创建了一个新的构建类型 jnidebug 并使用了 debug 构建模式
  • 继续配置 jnidebug , 启用了JNI组件的debug, 并添加了一个不同的包名前缀.

创建一个新的 BuildType 和在 buildTypes 容器中创建一个新元素一样简单. 可以调用 initWith() 或用括号来配置. 参见 DSL参考手册 来了解可用于配置构建类型的所有属性.

如果需要修改构建属性, BuildType 可以添加某些代码或资源. 对于每种构建类型, 都会创建一个与之匹配的 sourceSet , 默认路径在 src/<buildtypename>/ , 例如 src/debug/java 目录只能添加debug APK所用的资源. 这意味着 BuildType 名称不能使用 mainandroidTest (这是插件强制设置的), 并且名称必须唯一.

和其他资源目录一样, 构建类型资源目录页可以重新定义:

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

上面的代码将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 {
 ...
}

注意: 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 {
 ...
}

注意: jcenter() 是指定仓库URL的快捷方式. Gradle支持远程和本地仓库.

注意:Gradle会跟进所有依赖. 也就是说如果一个依赖有他自己的其他依赖, 这些依赖也会被拉取.

多项目设置

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的内容很简单. 它定义了哪个目录是一个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"
}

以上代码创建了一个库项目, 并使用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"
}

注意这个发布配置名称引用了完整的变量名. releasedebug 仅适用于没有渠道的情况. 如果你希望在使用渠道时改变默认发布变量, 你可以这样写:

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/ .

@todo

解决main apk和test apk的冲突

@todo

运行测试

如之前所说, check需要一个已连接的设备才能执行, 它通过一个叫做 connectedCheck 的锚点任务来执行. 这基于 connectedDebugAndroidTest 任务. 该任务执行以下内容:

  • 保证app和测试app被构建 (基于 assebleDebug 和 assebleDebugAndroidTest )
  • 安装两个app
  • 运行测试
  • 卸载两个app

如果连接了多个设备, 所有测试会在所有已连接设备上并行运行. 如果任一个设备上的任一测试失败, 整个构建都会失败

测试Android库

@todo

测试报告

当运行单元测试时, Gradle输出一个HTML报告来查看结果. Android插件根据此构建并扩展了HTML报告, 使其汇总所有连接设备的结果. 所有的测试结果存储在 build/reports/androidTests/ . 可以通过如下代码配置输出路径:

android {
 ...
 testOptions {
 resultsDir = "${project.buildDir}/foo/results"
 }
}

android.testOptions.resultsDir 的值通过 Project.file(String) )来获得

多项目报告

@todo

Lint支持

你可以为具体的variant运行lint, 例如 ./gradlew lintRelease , 也可以为所有variant运行lint, 例如 ./gradlew lint . 单独运行则产生单独的报告. 你可以添加lintOptions部分来配置lint.

android {
 lintOptions {
 // 关闭指定问题的检查
 disable 'TypographyFractions','TypographyQuotes'

 // 开启指定问题的检查
 enable 'RtlHardcoded','RtlCompat', 'RtlEnabled'

 // 只检查指定的问题
 check 'NewApi', 'InlinedApi'
 }
}

Build Variant

新构建系统的一个目的是为同一个应用创建不同的版本

主要有两种用例:

  1. 相同应用的不同版本
    例如, 一个免费/演示版本和一个”高级”付费版本
  2. 相同应用在Google Play商店中的多apk打包
    参见 多apk
  3. 以上两种情况的结合

我们的目标是可以用相同的项目生成不同的apk.

Product Flavor

product flavor定义了项目构建的自定义版本. 一个单独项目可以有多种flavor, 它可以改变所生成的应用

这种设计的目的是用于产生最小的区别. 如果问你”这些是相同的应用吗?”, 答案是”是”的话, 那么应该使用库项目.

Product flavor使用 productFlavors DSL容器来声明:

android {
 ....

 productFlavors {
 flavor1 {
 ...
 }

 flavor2 {
 ...
 }
 }
}

这会创建两种flavor, 名为 flavor1 和 flavor2 .

注意:flavor的名称不能与已存在的 Build Type 名称, 或是 androidTest , test 重复.

Build Type + Product Flavor = Build Variant

正如我们之前所见, 每个构建类型都会生成一个新的apk. Product Flavor也有同样作用: 项目的输出会是所有 Build Typeproduct 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
 }
 }
}

注意 android.productFlavors.* 对象和 android.defaultConfig 对象都是 ProductFlavor 类型的, 也就是说他们会共享相同的属性.

defaultConfig 为所有flavor提供了基本配置, 每种flavor可重写任意值. 在上面的例子中, 最终的配置结果是这样的:

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

通常情况下, 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 VariantBuild TypeProduct 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"
 ...
 }
 }
}

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的优先级.

测试

@todo

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

通过以上配置, 你的项目只有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'
 }
 }
}

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 TypeProduct 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
  • 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”

每种任务类型的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/