构建变体(gradle系列四)

背景

  • 当你在开发一个app,通常你会有几个版本。大多数情况是你需要一个开发版本,用来测试app和弄清它的质量,然后还需要一个生产版本。这些版本通常有不同的设置,例如不同的URL地址。更可能的是你可能需要一个免费版和收费版本。基于上述情况,你需要处理不同的版本:开发免费版,开发付费版本,生产免费版,生产付费版,而针对不同的版本不同的配置,这极大增加的管理难度。
  • Gradle有一些方便的方法来管理这些问题。我们很早之前谈过debug和release版本,现在我们谈到另外一个概念,不同的产品版本。构建版本和生产版本通常可以合并,构建版本和生产版本的合并版叫做构建变种。

构建版本

  • 在Gradle的Android插件中,一个构建版本意味着定义一个app或者依赖库如何被构建,每个构建版本都有特殊的需求,比如是否需要debug,applicationId是什么,是否需要删除无用的资源文件,通过buildTypes方法来定义一个构建版本。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    android {
    buildTypes {
    release {
    minifyEnabled false
    proguardFiles getDefaultProguardFile
    ('proguard-android.txt'), 'proguard-rules.pro'
    }
    }
    }
  • 这个文件定义了模块的release版本,然后定义了proguard的位置,该release版本不是唯一的构建版本,默认情况下,还有个debug版本,AndroidStudio把它视为默认的构建版本。

创建自己的构建版本

  • 创建构建版本只需要在buildTypes写入自己的版本,如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    android {
    buildTypes {
    staging {
    applicationIdSuffix ".staging"
    versionNameSuffix "-staging"
    buildConfigField "String", "API_URL",
    "\"http://staging.example.com/api\""
    }
    }
    }
  • 上面定义了一个staging版本,该版本定义了一个新的applicationid,这让其与debug和release版本的applicationId不同,假设使用了默认的配置,那么applicationID将会是这样:

    1. Debug:com.package
    2. Release: com.package
    3. Staging: com.package.staging
  • 构建类型默认的属性和值如下图所示:
    buildTypes

  • 这意味着你可以在你的设备上安装staging版本和release版本,但是你可以继承已有的版本:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    android {
    buildTypes {
    staging.initWith(buildTypes.debug)
    staging {
    applicationIdSuffix ".staging"
    versionNameSuffix "-staging"
    debuggable = false
    }
    }
    }
    // initWith()方法创建了一个新的版本的同时,复制所有存在的构建版本,类似于继承。

Source sets

  • 当你创建了一个新的构建版本,Gradle也创建了新的source set。使用source set的目的是将一些源文件组合起来,为了某个特殊目的在逻辑上进行分组,方便对文件进行管理。默认情况下,该文件夹不会自动为你创建,所以要手工创建。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    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
  1. 当添加了一个java类到staging版本,你可以添加相同的类到debug和release版本,但是不能添加到main版本,否则会抛出异常。
  • 当使用不同的source sets时,资源文件的处理需要特殊的方式,Drawable和layout文件将会覆盖在main中的重名文件,但是values文件夹下的资源不会,gradle将会把这些资源连同main里面的资源一起合并。举个例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    在main中创建了一个string.xml
    <resources>
    <string name="app_name">TypesAndFlavors</string>
    <string name="hello_world">Hello world!</string>
    </resources>
    在staging版本也添加了string.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,最终的string.xml将会是main目录下的strings.xml
  • manifest和value的情况一样,Android插件会合并他们,比如,你在staging下面创建一个manifest,那么你只需要添加自己的标签,不需要去拷贝在main下面的mainfest文件。

依赖包

  • 每一个构建版本都有自己的依赖包,如果你想为debug版本添加一个logging框架,可以这么做:
    1
    2
    3
    4
    5
    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(产品版本)

  • 和构建版本不同,product flavors用来为一个app创建不同的版本。典型的例子是,一个app有付费和免费版。它简化了基于相同的代码构建不同版本的app。

创建product flavors

  • productFlavors代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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都分享一样的属性。

多个flavor构建变体

  • 在某些场景下,你可能需要创建一些product flavors的合并版本,举个例子:client A 和client B都需要一个free和paid版本,而他们又都基于一样的代码,但是不一样的颜色,如果创建4个不同的flavors意味着重复的配置,合并flavors最简单的做法可能是使用flavor dimensions:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    android {
    flavorDimensions "color", "price"
    productFlavors {
    red {
    flavorDimension "color"
    }
    blue {
    flavorDimension "color"
    }
    free {
    flavorDimension "price"
    }
    paid {
    flavorDimension "price"
    }
    }
    }
  • 当你添加了flavor dimensions,你就需要为每个flavor添加flavorDimension,否则会提示错误。flavorDimensions定义了不同的dimensions,当然其顺序也很重要。当你合并二个不同的flavors时,他们可能有一样的配置和资源。例如上例:

    1. blueFreeDebug and blueFreeRelease
    2. bluePaidDebug and bluePaidRelease
    3. redFreeDebug and redFreeRelease
    4. redPaidDebug and redPaidRelease

构建变体

  • 构建变体是构建版本和生产版本的结合体,可以在AndroidStudio左下角的BuildVariants中查看。
  • 如果没有product flavors,那么变体就只包含构建版本,即使没有定义任何构建版本,AndroidStudio 也会默认为你创建debug版本的。

tasks

  • Android插件会为每一个变体创建不同的配置,一个新的Android项目会有debug和release版本,所以可以使用assembleDebug和assembleRelease,也可以只使用assemble,debug和release都会执行,当添加了新的构建版本或者生产版本,同样的也会创建新的task。以上面的productFlavors为例:
    1. assembleBlue uses the blue flavor configuration and assembles both BlueRelease and BlueDebug.
    2. assembleBlueDebug combines the flavor configuration with the build type configuration, and the flavor settings override the build type settings.

Source sets

  • 构建变体也可以有自己的资源文件夹,比如,上面列子,可以有src/blueFreeDebug/java/

资源文件和manifest的合并

  • 在打包app之前,Android插件会合并main中的代码和构建的代码,当然,依赖项目也可以提供额外的资源,他们也会被合并,举个例子:你不想在main中申明这个权限,因为可能会导致一些问题,所以你可以添加一个额外的manifest文件在debug的文件夹中,申请额外的权限。资源和manifest的优先级是这样的:
    资源优先级
  • 如果一个资源在main中和在flavor中定义了,那么那个在flavor中的资源有更高的优先级。这样那个在flavor文件夹中的资源将会被打包到apk。而在依赖项目申明的资源总是拥有最低优先级。

创建构建变体

  • gradle让处理构建变体变得更加容易:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    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"
    }
    }
    }

变体过滤器

  • 忽略某个变体是可行的,这样你可以加速你的构建,当使用assemble时候,将不会执行你不需要的变体,在build.gradle中添加代码:
    1
    2
    3
    4
    5
    6
    7
    8
    android.variantFilter { variant ->
    if(variant.buildType.name.equals('release')) {
    variant.getFlavors().each() { flavor ->
    if (flavor.name.equals('blue')) { variant.setIgnore(true);
    }
    }
    }
    }

签名配置

  • 在发布你的应用之前,你需要为你的app私钥签名,如果你有付费版和免费版,你需要有不同的key去签名不同的变体,配置签名可以这样定义:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    android {
    signingConfigs {
    staging.initWith(signingConfigs.debug)
    release {
    storeFile file("release.keystore")
    storePassword"secretpassword"
    keyAlias "gradleforandroid"
    keyPassword "secretpassword"
    }
    }
    }
  • 上面这个例子,我们创建了2个不同的签名配置,debug配置是AndroidStudio默认的,其使用了公共的keystore和password。release配置默认使用了storeFile,定义了key alias和password。

  • 在构建版本有一个属性叫做singingConfig,可以这么配置:

    1
    2
    3
    4
    5
    6
    7
    android {
    buildTypes {
    release {
    signingConfig signingConfigs.release
    }
    }
    }
  • 假如你需要对每个版本生成不同的验证,你可以这么定义:

    1
    2
    3
    4
    5
    6
    7
    android {
    productFlavors {
    blue {
    signingConfig signingConfigs.release
    }
    }
    }
  • 但是上面也提到,buildType的优先级高于flavor,所以在flavor中定义可能会被覆盖,最好的方式如下:

    1
    2
    3
    4
    5
    6
    7
    8
    android {
    buildTypes {
    release {
    productFlavors.red.signingConfig signingConfigs.red
    productFlavors.blue.signingConfig signingConfigs.blue
    }
    }
    }