5 构建变种(Build Variants)

新构建系统的一个目标是使同一个应用可以创建不同的版本。

有两种主要的使用情况:

  1. 同一个应用的不同版本。例如:一个免费/示例版本与收费的专业版本。
  2. 同一个应用打包成多个不同APK发布在Google Play Store。查看http://developer.android.com/google/play/publishing/multiple-apks.html获取更多信息。
  3. 1和2的组合。

该目标使得同一个项目能够生成这些不同的APK,而不是通过使用一个库项目和2个以上的应用项目。

5.1 产品标识(Product flavors)

一个产品标识(Product Flavor)定义了从项目中构建一个应用的定制版本。一个项目可以有不同的标识(flavor)来变更生成的应用。

这个新概念旨在帮助应用之间差异非常小的项目进行构建。如果问题“这是同一个应用吗?”的答案是“是”的话,那么可能需要去复习下库项目的知识。

产品标识(Product Flavor)使用productFlavorsDSL容器来声明:

android {
    ...

    productFlavors {
        flavor1 {
            ...
        }

        flavor2 {
            ...
        }
    }
}

这里创建了两个标识(flavor),名为flavor1flavor2

注意:标识(flavor)的名称不能与已存在的构建类型(Build Type)名称,或者androidTesttest源码集合(sourceSet)冲突。

5.2 构建类型 + 产品标识 = 构建变种(Build Type + Product Flavor = Build Variant)

正如我们之前所了解的,每个构建类型(Build Type)生成一个新的APK,产品标识(Product Flavor)也是一样:项目的输出变成了构建类型(Build Type)与产品标识(Product Flavor)(如果存在)的所有可能组合。每个组合(构建类型(Build Type)、产品标识(Product Flavor))称之为构建变种(Build Variant)。例如,与默认的debugrelease构建类型(Build Type)组合,上面例子生成的构建变种(Build Variant)为:

  • Flavor1 - debug
  • Flavor1 - release
  • Flavor2 - debug
  • Flavor2 - release

没有产品标识(Product Flavor)的项目也是有构建变种(Build Variant)的,但使用的是默认的标识(flavor)/配置,是没有名字的,使得变种(variant)列表与构建类型(Build Type)列表相同。

5.3 产品标识配置(Product Flavor Configuration)

每个标识(flavor)使用闭包来配置:

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为所有标识(flavor)提供基本配置,每个标识(flavor)可以重新配置这些值。在上面的例子中,最终的配置为:

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

通常情况下,构建类型(Build Type)会覆盖其他的配置。例如,构建类型(Build Type)的packageNameSuffix会添加到产品标识(Product Flavor)的packageName后缀。同时也存在一个配置可以在构建类型(Build Type)和产品标识(Product Flavor)上同时配置的一些情况。在这种情况下,根据具体情况进行配置。例如,signingConfig是这些属性的一个示例。我们可以在android.buildTypes.release.signingConfig上配置同样的SigningConfig到所有发布包,也可以分别通过设置android.productFlavors.*.signingConfig来使用自己的SigningConfig到发布包。

5.4 源码集合和依赖关系(Sourcesets and Dependencies)

与构建类型(Build Type)类似,产品标识(Product Flavor)也可以通过自己的源码集合提供代码和资源。上面的例子创建了4个源码集合:

  • 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和构建类型(Build Type)源码集合一起用来构建APK。当构建一个APK时,使用以下规则来处理所有源码集合:

  • 所有源代码(src/*/java)作为多目录结构合并生成一个输出。
  • 所有清单(manifest)合并为一个清单(manifest)。与构建类型(Build Type)类似,同样允许产品标识(Product Flavor)有不同的组件或权限。
  • 所有的资源(res和assets)的优先级为构建类型(Build Type)覆盖产品标识(Product Flavor),覆盖main源码集合。
  • 每个构建变种(Build Variant)从自己的资源中生成自己的R类,构建变种(Build Variant)之间不共享任何东西。

最后,与构建类型(Build Type)一样,产品标识(Product Flavor)可以有自己的依赖关系。例如,如果标识(flavor)用于生成一个广告版本应用和付费版本应用,其中广告版本可以依赖于广告SDK,而另一个则不依赖:

dependencies {
    flavor1Compile "..."
}

在这示例中,src/flavor1/AndroidManifest.xml文件可能需要包含访问网络权限。

每个变种(variant)也会创建额外的源码集合:

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

这些变种(variant)比构建类型(Build Type)的源码集合有更高的优先级,也允许自定义定制。

5.5 构建和任务(Building and Tasks)

我们之前了解到了每个构建类型(Build Type)都创建自己的assemble\任务,但是构建变种(Build Variant)是构建类型(Build Type)和产品标识(Product Flavor)的组合。

当使用产品标识(Product Flavor)时,将创建更多的assemble类型任务,分别是:

  1. assemble\
  2. assemble\
  3. assemble\

#1 允许直接构建一个变种(variant)。例如assembleFlavor1Debug

#2 允许构建指定构建类型(Build Type)的所有APK。例如assembleDebug任务构建Flavor1DebugFlavor2Debug两个变种(variant)

#3 允许构建指定的产品标识(Product Flavor)的所有APK。例如assembleFlavor1任务构建Flavor1DebugFlavor1Release两个变种(variant)

assemble任务会构建所有可能的变种(variant)。

5.6 多标识变种(Multi-flavor variants)

在一些情况下,一个应用可能需要基于多个标准创建不同版本。例如,Google Play中的multi-apk支持提供4种不同的过滤器。可以通过多维的产品标识(Product Flavor)来创建不同的APK,用于区分不同的过滤需求。

我们可以考虑一个需要示例版本和支付版本,以及需要在multi-apk支持中使用ABI过滤器的游戏例子。由于存在3种ABI以及2个版本,所以需要生成6个APK(这里并未包含不同的构建类型(Build Type))。然而,支付版本的源代码在3种ABI中都是一致的,所以不能简单地创建6个标识(flavor)。相反,这里需要使用二维标识(flavor),然后变种(variant)会自动构建所有可能的组合。

该特性通过标识维度(Flavor Dimensions)实现。标识(flavor)需要分配一个具体维度:

android {
    ...

    flavorDimensions "abi", "version"

    productFlavors {
        freeapp {
            dimension "version"
            ...
        }

        paidapp {
            dimension "version"
            ...
        }

        arm {
            dimension "abi"
            ...
        }

        mips {
            dimension "abi"
            ...
        }

        x86 {
            dimension "abi"
            ...
        }
    }
}

android.flavorDimensions数组定义了可能的维度及其顺序。每个已定义的产品标识(Product Flavor)分配到一个维度。

根据以上代码生成的产品标识(Product Flavor)维度[freeapp,paidapp]和[x86,arm,mip],以及构建类型(Build Type)[debug,release],生成以下变种(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定义的维度顺序是很重要的。

每个变种(variant)通过多个产品标识(Product Flavor)来配置:

  • android.defaultConfig
  • ABI维度其中之一
  • 版本维度其中之一

维度的顺序决定了哪个标识(flavor)覆盖哪个标识(flavor),这对于标识(flavor)中的值会替换掉在低优先级标识(flavor)中定义的值的资源而言是非常重要的。

标识(flavor)维度越早定义则有更高的优先级,所以在该实例中:

abi > version > defaultConfig

多标识(multi-flavor)项目也有额外的源码集合,与变种(variant)源码集合相似,但不包含构建类型(Build Type):

  • android.sourceSets.x86Freeapp Location src/x86Freeapp/
  • android.sourceSet.armPaidapp Location src/armPaidapp/
  • 等等...

这提供了在标识组合(flavor-combination)层面上的定制。标识组合(flavor-combination)源码集合的优先级高于基本的标识(flavor)源码集合,但低于构建类型(Build Type)源码集合。

5.7 测试(Testing)

测试多标识(multi-flavor)项目类似于简单标识(flavor)项目。

androidTest源码集合用于所有标识(flavor)的常用测试,同时每个标识(flavor)有自己的测试源码集合。

如上所述,每个标识(flavor)的测试源码集合会被创建:

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

同样的,它们也有自己的依赖关系:

dependencies {
    androidTestFlavor1Compile "..."
}

运行测试可以通过deviceCheck锚任务,或者当标识(flavor)被使用时充当锚任务的androidTest任务。

每个标识(flavor)都有自己的任务来运行测试:androidTest\,例如:

  • androidTestFlavor1Debug
  • androidTestFlavor2Debug

同样的,测试APK的构建任务和安装/卸载任务是相对于每个变种(variant):

  • assembleFlavor1Test
  • installFlavor1Debug
  • installFlavor1Test
  • uninstallFlavor1Debug
  • ...

最终的HTML报告生成支持根据标识(flavor)分类。

测试结果和报告的位置如下,先是每个标识(flavor)版本,然后是所有版本集合:

  • build/androidTest-results/flavors/
  • build/androidTest-results/all/
  • build/reports/androidTests/flavors
  • build/reports/androidTests/all/

自定义路径只会变更根目录,始终会创建每个标识(flavor)版本以及所有版本集合的报告和结果的子目录。

5.8 BuildConfig

当编译时,Android Studio生成一个BuildConfig类,其中包含了构建一个特别变种(variant)时使用的常量值。你可以通过检查这些常量值来调整不同变种(variant)间的表现,例如:

private void javaCode() {
    if (BuildConfig.FLAVOR.equals("paidapp")) {
        doIt();
    } else {
        showOnlyInPaidAppDialog();
    }
}

以下是BuildConfig中包含的常量值:

  • boolean DEBUG - 构建是否测试版本
  • int VERSION_CODE
  • String VERSION_NAME
  • String APPLICATION_ID
  • String BUILD_TYPE - 构建类型(Build Type)名称,如“release”
  • String FLAVOR - 标识(flavor)名称,如“paidapp”

如果项目使用多维标识,会生成额外的值。如以上的例子,会生成以下BuildConfig

  • String FLAVOR = "armFreeapp"
  • String FLAVOR_abi = "arm"
  • String FLAVOR_version = "freeapp"

5.9 过滤变种(Filtering Variants)

当你使用维度和标识(flavor)时,你可以以没有意义的变种(variant)结尾。例如你可以定义一个使用你的Web API的标识(flavor),和一个使用硬编码的假数据来测试的标识(flavor)。第二个标识(flavor)只用于开发过程,不需要构建发布版本。你可以使用variantFilter来删除该变种(variant):

android {
    productFlavors {
        realData
        fakeData
    }

    variantFilter { variant ->
        def names = variant.flavors*.name

        if(names.contains("fakeData") && variant.buildType.name == "release") {
            variant.ignore = true
        }
    }
}

使用以上配置,你的项目只有三个变种(variant):

  • realDataDebug
  • realDataRelease
  • fakeDataDebug

查看DSL引用获取variant中你可以检查的所有属性。