当你在开发一个 app,通常你会有几个版本。大多数情况是你需要一个开发版本,用来测试 app 和弄清它的质量,然后还需要一个生产版本。这些版本通常有不同的设置,例如不同的 URL 地址。更可能的是你可能需要一个免费版和收费版本。基于上述情况,你需要处理不同的版本:开发免费版,开发付费版本,生产免费版,生产付费版,而针对不同的版本不同的配置,这极大增加的管理难度。
Gradle 有一些方便的方法来管理这些问题。我们很早之前谈过 debug 和 release 版本,现在我们谈到另外一个概念,不同的产品版本。构建版本和生产版本通常可以合并,构建版本和生产版本的合并版叫做构建变种。
这一章我们将学习构建版本,它能使得开发更有效率,并且学习如何使用它们。然后我们会讨论构建版本和生产版本的不同,以及如何将其合并。我们会探讨签名机制,如何针对不同的变种签名等。
在这一章,我们遵循如下规则:
在 Gradle 的 Android 插件中,一个构建版本意味着定义一个 app 或者依赖库如何被构建。每个构建版本都要特殊的一面,比如是否需要 debug, application id 是什么,是否不需要的资源被删除等等。你可以定义一个构建版本通过 buildTypes 方法。例如:
android {
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile
('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
这个文件定义了该模块是 release 版本,然后定义了 proguard 的位置。该 release 版本不是唯一的构建版本,默认情况下,还有个 debug 版本。 Android studio 把它视为默认构建版本。
当默认的构建版本不够用的时候,创建版本也是很容易的一件事,创建构建版本你只需要在 buildTypes 写入自己的版本。如下所示:
android {
buildTypes {
staging {
applicationIdSuffix ".staging"
versionNameSuffix "-staging"
buildConfigField "String", "API_URL",
"\"http://staging.example.com/api\""
}
}
}
我们定义了一个 staging 版本,该版本定义了一个新的 application id,这让其与 debug 和 release 版本的 applicationID 不同。假设你使用了默认的配置,那么 applicationID 将会是这样的:
这意味着你可以在你的设备上安装 staging 版本和 release 版本。staging 版本也有自己的版本号。buildConfigField 定义了一个新的 URL 地址。你不必事事都去创建,所以最可能的方式是去继承已有的版本。
android {
buildTypes {
staging.initWith(buildTypes.debug)
staging {
applicationIdSuffix ".staging"
versionNameSuffix "-staging"
debuggable = false
}
}
}
initWith() 方法创建了一个新的版本的同时,复制所有存在的构建版本,类似继承。我们也可以复写该存在版本的所有属性。
当你创建了一个新的构建版本,Gradle 也创建了新的 source set。默认情况下,该文件夹不会自动为你创建,所有你需要手工创建。
app
└── src
├── debug
│ ├── java
│ │ └── com.package
│ │
│ ├── res
│ │ └── layout
│ │ └── activity_main.xml
│ └── AndroidManifest.xml
├── main
│ ├── java
│ │ └── com.package
│ │
│ ├── res
└── MainActivity.java
└── Constants.java
│ │
│ │
│ │
│ └── AndroidManifest.xml
├── staging
│ ├── java
│ │ └── com.package
├── drawable
└── layout
└── activity_main.xml
│ │
│ ├── res
│ │ └── layout
│ │ └── activity_main.xml
│ └── AndroidManifest.xml
└── release
├── java
│ └── com.package
│ └── Constants.java
└── AndroidManifest.xml
注意:当你添加一个Java类的时候,你需要知道以下过程,当你添加了 CustomLogic.java 到 staging 版本,你可以添加相同的类到 debug 和 release 版本,但是不能添加到main版本。如果你添加了,会抛出异常。
当使用不同的 source sets 的时候,资源文件的处理需要特殊的方式。 Drawables 和 layout 文件将会复写在 main 中的重名文件,但是 values 文件下的资源不会。gradle 将会把这些资源连同main里面的资源一起合并。
举个例子,当你在 main 中创建了一个 srings.xml 的时候:
<resources>
<string name="app_name">TypesAndFlavors</string>
<string name="hello_world">Hello world!</string>
</resources>
当你在你的 staing 版本也添加了 rings.xml:
<resources>
<string name="app_name">TypesAndFlavors STAGING</string>
</resources>
然后合并的 strings.xml 将会是这样的:
<resources>
<string name="app_name">TypesAndFlavors STAGING</string>
<string name="hello_world">Hello world!</string>
</resources>
当你创建一个新的构建版本而不是 staging,最终的 strings.xml 将会是 main 目录下的 strings.xml。
manifest 也和 value 文件下的文件一样。如果你为你的构建版本创建了一个 manifest 文件,那么你不必要去拷贝在 main 文件下的 manifest 文件,你需要做的是添加标签。Android 插件将会为你合并它们。
我们将在会之后的章节讲到合并的更多细节。
每一个构建版本都有自己的依赖包,gradle 自动为每一个构建的版本创建不同的依赖配置。如果你想为 debug 版本添加一个 logging 框架,你可以这么做:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:22.2.0'
debugCompile 'de.mindpipe.android:android-logging-log4j:1.0.3'
}
你可以结合不同的构建版本着不同的构建配置,就像这种方式,这让你的不同版本的不同依赖包成为可能。
和构建版本不同,product flavors 用来为一个 app 创建不同版本。典型的例子是,一个 app 有付费和免费版。product flavors 极大简化了基于相同的代码构建不同版本的 app。
如果你不确定你是否需要一个新的构建版本或者 product flavors,你应该问你自己,你是否需要内部使用和外部使用的 apk。如果你需要一个完全新的 app 去发布,和之前的版本完全隔离开,那么你需要 product flavors。否则你只是需要构建版本。
创建 product flavors 非常的容易。你可以在 productFlavors 中添加代码:
android {
productFlavors {
red {
applicationId 'com.gradleforandroid.red'
versionCode 3
}
blue {
applicationId 'com.gradleforandroid.blue'
minSdkVersion 14
versionCode 4
}
}
}
product flavors 和构建版本的配置不同。因为 product flavors 有自己的 ProductFlavor 类,就像 defaultConfig,这意味着你的所有 productFlavors 都分享一样的属性。
就像构建版本一样,product Flavors 也有自己的代码文件夹。创建一个特殊的版本就像创建一个文件夹那么简单。举个例子,当你有的生产版本的 blue flavors 有一个不同的 app 图标,该文件夹需要被叫做 blueRelease。
在一些例子中,你可能需要创建一些 product flavors 的合并版本。举个例子,client A 和 client B 可能都想要一个 free 和 paid 的版本,而他们又都是基于一样的代码,但是有不一样的颜色等。创建四个不同的 flavors 意味着有重复的配置。合并 flavors 最简单的做法可能是使用 flavor dimensions,就像这样:
android {
flavorDimensions "color", "price"
productFlavors {
red {
flavorDimension "color"
}
blue {
flavorDimension "color"
}
free {
flavorDimension "price"
}
paid {
flavorDimension "price"
}
}
}
当你添加了 flavor dimensions,你就需要为每个 flavor 添加 flavorDimension,否则会提示错误。flavorDimensions 定义了不同的 dimensions,当然其顺序也很重要。当你合并二个不同的 flavors 时,他们可能有一样的配置和资源。例如上例:
构建变体是构建版本和生产版本的结合体。当你创建了一个构建版本或者生产版本,同样的,新的变体也会被创建。举个例子,当你有 debug 和 release 版本,你创建了 red 和 blue 的生产版本,那么变体将会有四个:
你可以在 Android studio 的左下角找到它,或者通过 VIEW|Tool Windows|Build Variants 打开它。该视图列出了所有的变体,并且允许你去切换它们。改变他们将会影响到你按 Run 按钮。
如果你没有 product flavors,那么变体只是简单的包含构建版本,就算你没有定义任何构建版本,Android studio 也会默认为你创建 debug 版本的。
android 插件回味每一个变体创建不同的配置。一个新的 Android 项目会有 debug 和 release 版本,所有你可以使用 assembleDebug 和 assembleRelease,当然当你使用 assemble 命令,会二者都执行。当你添加了一个新的构建版本,新的task也会被创建。例如:
构建变体也可以有自己的资源文件夹,举个例子,你可以有 src/blueFreeDebug/java/。
在打包 app 之前,Android 插件会合并 main 中的代码和构建的代码。当然,依赖项目也可以提供额外的资源,它们也会被合并。你可能需要额外的 Android 权限针对 debug 变体。举个例子,你不想在 main 中申明这个权限,因为这可能导致一些问题,所以你可以添加一个额外的 mainfest 文件在 debug 的文件夹中,申明额外的权限。
资源和 mainfests 的优先级是这样的:
如果一个资源在 main 中和在 flavor 中定义了,那么那个在 flavor 中的资源有更高的优先级。这样那个在 flavor 文件夹中的资源将会被打包到 apk。而在依赖项目申明的资源总是拥有最低优先级。
gradle让处理构建变体变得容易。
android {
buildTypes {
debug {
buildConfigField "String", "API_URL",
"\"http://test.example.com/api\""
}
staging.initWith(android.buildTypes.debug)
staging {
buildConfigField "String", "API_URL",
"\"http://staging.example.com/api\""
applicationIdSuffix ".staging"
}
}
productFlavors {
red {
applicationId "com.gradleforandroid.red"
resValue "color", "flavor_color", "#ff0000"
}
blue {
applicationId "com.gradleforandroid.blue"
resValue "color", "flavor_color", "#0000ff"
}
}
}
在这个例子中,我们创建了 4 个变体,分别是 blueDebug,blueStaging,redDebug,redStaging。每一个变体都有其不同的 api url 以及颜色。例如:
忽略某个变体也是可行的。这样你可以加速你的构建当使用 assemble 的时候,这样你列出的 tasks 将不会执行那么你不需要的变体。你可以使用过滤器,在 build.gradle 中添加代码如下所示:
android.variantFilter { variant ->
if(variant.buildType.name.equals('release')) {
variant.getFlavors().each() { flavor ->
if (flavor.name.equals('blue')) { variant.setIgnore(true);
}
}
}
}
在这个例子中,我们检查下:
你可以看到 blueFreeRelease 和 bluePaidRelease 被排除在外,如果你运行 gradlew tasks,你会发现所有的关于上述变体的 tasks 不再存在。
在你发布你的应用之前,你需要为你的 app 私钥签名。如果你有付费版和免费版,你需要有不同的 key 去签名不同的变体。这就是配置签名的好处。配置签名可以这样定义:
android {
signingConfigs {
staging.initWith(signingConfigs.debug)
release {
storeFile file("release.keystore")
storePassword"secretpassword"
keyAlias "gradleforandroid"
keyPassword "secretpassword"
}
}
}
在这个例子中,我们创建了 2 个不同的签名配置。debug 配置是 as 默认的,其使用了公共的 keystore 和 password,所以没有必要为 debug 版本创建签名配置了。staging 配置使用了 initWith() 方法,其会复制其他的签名配置。这意味着 staging 和 debug 的 key 是一样的。
release 配置使用了 storeFile,定义了 key alias 和密码。当然这不是一个好的选择,你需要在 Gradle properties 文件中配置。
当你定义了签名配置后,你需要应用它们。构建版本都有一个属性叫做 signingConfig,你可以这么干:
android {
buildTypes {
release {
signingConfig signingConfigs.release
}
}
}
上例使用了 buildTypes,但是你可能需要对每个版本生成不同的验证,你可以这么定义:
android {
productFlavors {
blue {
signingConfig signingConfigs.release
}
}
}
当然,你在 flavor 中定义这些,最好会被重写,所以最好的做法是:
android {
buildTypes {
release {
productFlavors.red.signingConfig signingConfigs.red
productFlavors.blue.signingConfig signingConfigs.blue
}
}
}
在这一章,我们讨论了构建版本和生产版本,以及如何结合它们。这将会是非常有用的工具,当你需要不同的 url 以及不同的 keys,而你们有相同的代码和资源文件,但是有不同的类型以及版本,构建版本和生产版本将会让你的生活更美好。
我们也谈论了签名配置以及如何使用他们。
下一章,你将会学习到多模块构建,因为当你想把你的代码分成一个依赖包或者依赖项目的时候,或者你想把 Android wear 模块放在你的应用的时候,这将非常重要。