65.9K
CodeProject 正在变化。 阅读更多。
Home

Android 产品变体和配置

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2016年4月11日

CPOL

7分钟阅读

viewsIcon

41994

本文将深入探讨 Android 的产品风味。学习如何从相同的源代码创建多个 APK。创建、自定义、分组、配置和过滤产品风味。

引言

产品风味(Product flavors)存在已经有一段时间了。我认为产品风味是 Android Studio 最酷的功能之一。产品风味允许我们从相同的源代码创建应用的多个版本,例如免费版、付费版、试用版等(例如《愤怒的小鸟》免费版和《愤怒的小鸟》高清版),而无需复制代码。本文将帮助您理解产品风味的应用程序、分组、配置和过滤。

背景

本文的前提是您具备 Android 开发的基本知识。如果产品风味对您来说是陌生的,请不要担心,本文将教您关于它的一切。

定义产品风味

产品风味定义在模块的 build.gradle 文件中的 Android block 中。定义方式如下:

android {...
    defaultConfig {...}
    productFlavors {
        flavor1 {...}
        flavor2 {...}
        flavor3 {...}
    }
    ...
    buildTypes{...}
}

如您所见,这个骨架放在 build.gradle 文件中的 Android block 里。通过编写这个,我们创建了 3 个风味,分别称为 flavor1flavor2flavor3。默认情况下,Android 有 2 种调试类型,称为 debugrelease(还可以添加更多,如 jnidebug)。构建变体(build variant)是构建类型和产品风味的组合。因此,现在将有 6 个构建变体:flavor1-debugflavor1-releaseflavor2-debugflavor2-releaseflavor3-debugflavor3-release。在这些风味块内是风味特定的属性和方法,例如 applicationId(应用包名)、versionCodebuildConfigField 等。稍后我们将看到这些。单击此处查看用于配置风味特定属性和方法的 ProductFlavor DSL(领域特定语言)对象。

现在,我们都知道默认配置(default config),我们在该块中指定 minSdkVersionversionCode 等属性。默认配置块中定义的任何属性和方法都会被所有产品风味继承。默认配置块也使用 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。它用于为每个风味分配不同的包名标识符。这样可以确保您的应用的不同的构建变体(或版本)可以安装在同一设备上。在上面的示例中,flavor123 生成的 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 特定目录中的 drawablemipmap 文件夹中(取决于您使用的是哪个)。

多风味变体

到目前为止,我们所做的一切在当应用变体仅由一个维度决定时都很有用。例如,假设维度是价格,您可以创建 freepaid 等风味。如果需求是基于多个维度创建变体,例如对于 environment 维度,可以有三个风味:“dev”、“staging” 和 “production”,对于价格维度有三个风味:“free”、“freemium” 和 “paid”,可能还有其他维度。在这种情况下,您可以选择这六个风味中的任何一个,加上 debug 或 release(默认的构建类型)。但是我们关心的产品风味同时取决于这两个维度。例如 free-dev-debugpaid-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{...}
}

在这个示例中,我们可以看到有两个风味维度,countryprice。Country 风味维度有三个风味:IndiaChinaRussia;price 有两个风味:freepro。这反过来又为我们创建了 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

如您所见,只需定义 flavorDimensionsdimension 属性,就可以对产品风味进行分组,Android 会创建所有维度类型和构建类型的风味组合的构建变体。这些变体体现在各个地方,包括 Android Studio 左下角的构建变体标签。

过滤产品风味

现在上面的构建变体列表都是由 Android 生成的。如果我们想过滤这个列表怎么办?假设因为某些原因,我们不想要 Russia-freeIndia-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。
© . All rights reserved.