Android 产品变体和配置





5.00/5 (4投票s)
本文将深入探讨 Android 的产品风味。学习如何从相同的源代码创建多个 APK。创建、自定义、分组、配置和过滤产品风味。
引言
产品风味(Product flavors)存在已经有一段时间了。我认为产品风味是 Android Studio 最酷的功能之一。产品风味允许我们从相同的源代码创建应用的多个版本,例如免费版、付费版、试用版等(例如《愤怒的小鸟》免费版和《愤怒的小鸟》高清版),而无需复制代码。本文将帮助您理解产品风味的应用程序、分组、配置和过滤。
背景
本文的前提是您具备 Android 开发的基本知识。如果产品风味对您来说是陌生的,请不要担心,本文将教您关于它的一切。
定义产品风味
产品风味定义在模块的 build.gradle 文件中的 Android block 中。定义方式如下:
android {...
defaultConfig {...}
productFlavors {
flavor1 {...}
flavor2 {...}
flavor3 {...}
}
...
buildTypes{...}
}
如您所见,这个骨架放在 build.gradle 文件中的 Android block 里。通过编写这个,我们创建了 3 个风味,分别称为 flavor1
、flavor2
和 flavor3
。默认情况下,Android 有 2 种调试类型,称为 debug
和 release
(还可以添加更多,如 jnidebug
)。构建变体(build variant)是构建类型和产品风味的组合。因此,现在将有 6 个构建变体:flavor1-debug
、flavor1-release
、flavor2-debug
、flavor2-release
、flavor3-debug
和 flavor3-release
。在这些风味块内是风味特定的属性和方法,例如 applicationId
(应用包名)、versionCode
、buildConfigField
等。稍后我们将看到这些。单击此处查看用于配置风味特定属性和方法的 ProductFlavor
DSL(领域特定语言)对象。
现在,我们都知道默认配置(default config),我们在该块中指定 minSdkVersion
、versionCode
等属性。默认配置块中定义的任何属性和方法都会被所有产品风味继承。默认配置块也使用 ProductFlavor
DSL(领域特定语言)对象。这意味着任何可以放入默认配置块中的内容都可以放入风味块中。每个产品风味都可以覆盖默认配置块中定义的属性和方法。下面是一个示例骨架:
android {
...
defaultConfig {
applicationId "the.default.packagename"
minSdkVersion 8
versionCode 10
}
productFlavors {
flavor1 {
applicationId "the.default.packagename.flavor1"
minSdkVersion 15
}
flavor2 {
applicationId "the.default.packagename.flavor2"
versionCode 20
}
flavor3 {...}
}
...
buildTypes{...}
...
}
在上面的示例中,一些属性在风味块中被覆盖,其余的从默认配置块中继承。单击此处查看可以在默认配置块和风味块(或 groovy closure)中配置的所有属性和方法的列表。建议为每个风味分配不同的 applicationId
。它用于为每个风味分配不同的包名标识符。这样可以确保您的应用的不同的构建变体(或版本)可以安装在同一设备上。在上面的示例中,flavor1
、2
和 3
生成的 APK 可以安装在同一设备上。
产品风味的 SourceSets
如上配置 3 个产品风味会创建 6 个源集(source sets)
"src/flavor1" - android.sourceSets.flavor1
"src/flavor2" - android.sourceSets.flavor2
"src/flavor3" - android.sourceSets.flavor3
"src/androidTestFlavor1" - android.sourceSets.androidTestFlavor1
"src/androidTestFlavor2" - android.sourceSets.androidTestFlavor2
"src/androidTestFlavor3" - android.sourceSets.androidTestFlavor3
// All these folder follow the java/src/main/.. structure
// for any flavor and test specific customization.
要了解更多关于 Android 源集的信息,请查看http://goo.gl/NvAg74。
这种风味特定的文件夹结构是风味特定代码的存放位置。它不是默认生成的,您必须创建与主结构完全相同的结构。如果您想保留自己的结构,只需在 build.gradle 文件中覆盖 sourceSets
(参见http://goo.gl/NvAg74)。flavor1
的自定义必须在 “src/flavor1/ {java, res, assets}” 文件夹内完成。公共代码位于 “src/main/…” 目录中。如果您想更改某个风味的应用名称,例如 flavor2
,您所要做的就是在 “src/flavor2/res/values/strings.xml” 中定义 app_name
(或者您在 manifest 中使用的作为应用名称的任何字符串资源)。只要您想覆盖,就不要将整个 XML 或 res 文件复制到风味特定的文件夹中,让 Android 资源合并器来处理。假设您使用 “@drawable/ico_app
或 @mipmap/ico_app
” 作为应用图标,这可以很容易地为每个风味配置,只需在各自的文件夹结构中放置风味特定的图标。例如,对于 flavor3
,只需将 flavor3
特定的图标命名为 ico_app
,并将其放在 flavor3
特定目录中的 drawable
或 mipmap
文件夹中(取决于您使用的是哪个)。
多风味变体
到目前为止,我们所做的一切在当应用变体仅由一个维度决定时都很有用。例如,假设维度是价格,您可以创建 free
、paid
等风味。如果需求是基于多个维度创建变体,例如对于 environment
维度,可以有三个风味:“dev
”、“staging
” 和 “production
”,对于价格维度有三个风味:“free
”、“freemium
” 和 “paid
”,可能还有其他维度。在这种情况下,您可以选择这六个风味中的任何一个,加上 debug 或 release(默认的构建类型)。但是我们关心的产品风味同时取决于这两个维度。例如 free-dev-debug
、paid-production-release
等。在这里,我们尝试对产品风味进行分组,这默认是不允许的。这可以通过产品风味的 dimension
属性启用。我们可以通过 Android 块的 flavorDimensions
属性来设置产品风味的维度,然后为每个风味分配一个维度。下面是一个示例:
android {...
flavorDimensions "country", "price"
productFlavors {
free {dimension "type"...}
pro {dimension "type"...}
India {dimension "country"...}
China {dimension "country"...}
Russia {dimension "country"...}
}
...
buildTypes{...}
}
在这个示例中,我们可以看到有两个风味维度,country
和 price
。Country 风味维度有三个风味:India
、China
和 Russia
;price 有两个风味:free
和 pro
。这反过来又为我们创建了 12 个构建变体。
India-free-debug
India-free-release
India-pro-debug
India-pro-release
China-free-debug
China-free-release
China-pro-debug
China-pro-release
Russia-free-debug
Russia-free-release
Russia-pro-debug
Russia-pro-release
如您所见,只需定义 flavorDimensions
和 dimension
属性,就可以对产品风味进行分组,Android 会创建所有维度类型和构建类型的风味组合的构建变体。这些变体体现在各个地方,包括 Android Studio 左下角的构建变体标签。
过滤产品风味
现在上面的构建变体列表都是由 Android 生成的。如果我们想过滤这个列表怎么办?假设因为某些原因,我们不想要 Russia-free
和 India-paid
变体。这可以通过根据某些条件忽略某些构建变体来实现。下面是忽略 Russia-free 变体的示例。
//Filtering variants
android {
...
variantFilter { variant ->
def names = variant.flavors*.name
def buildTypeName = variant.buildType.name
// if buildtype is required for filtering use
// the above field
if (names.contains("Russia") && names.contains("free")) {
variant.ignore = true
// or variant.setIgnore(true)
}
}
...
}
可以通过设置 ignore 字段或将 setIgnore
方法设置为 false
来忽略构建变体。这种全局过滤会体现在各个地方,包括 Android Studio 左下角的构建变体标签、assemble、install Gradle 任务等。上面的代码基本上遍历所有变体,并根据我们的自定义条件忽略其中一些变体。
风味特定的依赖项
您一定已经在项目中见过 testCompile “junit…”
。这是风味特定的依赖项。例如,junit 是一个测试库,不应该包含在 release APK 中(为什么要在 APK 中增加不需要的测试大小?)。在应用级别 build.gradle
的依赖项部分添加 “flavorCompile
” 语法会将该依赖项添加到特定风味中。如果您熟悉 Facebook 的 stetho 库,那么您就知道它仅用于开发目的,不应该包含在 release 版本中。这是 Android 产品风味带来的一个很棒的功能。一些示例:
testCompile 'junit:junit:4.12'
stethoFlavorCompile 'com.facebook.stetho:stetho:1.3.1'
BuildConfig 常量和资源值
现在我们知道风味特定的代码放在风味特定的 sourceSet
中,但有时我们需要在主代码库(“src/main/…
”)中使用风味特定的代码。问题在于:主源代码不知道将来会生成哪个风味。为此,有一个叫做 BuildConfig
的东西。它是一个自动生成的文件,不应该被修改。该文件包含风味特定的常量,可以在主源代码中使用。查看 ProductFlavor DSL 对象了解可用的属性和方法。您可以像这样设置风味特定的常量和资源:
android {...
flavorDimensions "country", "price"
productFlavors {
free {dimension "type"
buildConfigField("String", "featureList", "restricted")
resValue("boolean", "ads", "true")
...}
pro {dimension "type"
buildConfigField("String", "featureList", "all")
resValue("boolean", "ads", "false")
...}
India { applicationId "my.app.india"
dimension "country"
buildConfigField("String", "shortcode", "IN")
...}
China { applicationId "my.app.china"
dimension "country"
buildConfigField("String", "shortcode", "CHN")
...}
Russia { applicationId "my.app.russia"
dimension "country"
buildConfigField("String", "shortcode", "RU")
...}
}
...
buildTypes{...}
}
然后,这用于生成 BuildConfig
类和动态资源(请查看生成文件夹中的资源)。对于 Russian-pro-debug
构建变体(根据上面的风味定义),生成的构建配置将如下所示:
//Build config for Russia-pro-debug build variant
public final class BuildConfig {
public static final boolean DEBUG = Boolean.parseBoolean("true");
public static final String APPLICATION_ID = "my.app.russia";
public static final String BUILD_TYPE = "debug";
public static final String FLAVOR = "proRussia";
public static final int VERSION_CODE = 1;
public static final String VERSION_NAME = "1.0";
public static final String FLAVOR_price = "pro";
public static final String FLAVOR_country = "Russia";
// Fields from product flavor: pro
public static final String featureList = "all";
// Fields from product flavor: Russia
public static final String shortcode = "RU";
}
如您所见,这些常量特定于所选的风味。然后可以在主源代码中使用它们。是不是很简单?构建配置文件位于此位置:“<app 或 module>\build\generated\source\ buildConfig\<variant>\<debug 或 release>\my\app\package“
。
动态 Manifest
有时,我们需要在 manifest 文件中使用应用的包名。但是,随着产品风味的使用,应用的包名或 application ID 不再是固定的,它会随着选定的风味而变化。在这种情况下,我们可以使用 groovy 语法在 manifest 中使包名动态化。通过这样做,application ID 将在运行时从 build.gradle
中获取。下面是一个示例:
<manifest xmlns:android="http://schemas.android.com/apk/res/android......
...
<permission
android:name="${applicationId}.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
...
<application...
这是一个用于 Android GCM 的声明权限的示例。请注意 applicationId
周围的 groovy 语法,那就是魔术发生的地方。
关注点
如果在多风味变体中,常量、包名或资源出现冲突,需要牢记的通用规则是“具体风味覆盖通用风味”。例如,当生成 India-Free-Debug
变体时,在 free
风味中定义的应用程序图标将被 India-free
风味中定义的图标覆盖。
要生成所有变体的 debug APK,只需打开 Android Studio 右侧的 gradle 标签,查找名为 “assembleDebug
” 的任务,然后双击它。或者在 Android Studio 终端中输入 “gradlew -q :app:assembleDebug
”。
历史
- 1.0 - Kaushal Dhruw 的基础版本
- 1.1 - 删除了令人困惑的术语 flavor 和 flavours。