Gradle For Android(4)--构建不同的版本

介绍

当构建App的时候,通常都会有不同的版本。比如说测试版本,正式版本,Debug版本等等。而这些版本通常有不同的配置,比如说服务器的域名,Log开关,付费开关等等特性。

之前我们看到了Release以及Debug版本的概念,而接下来会介绍product flavors的概念。而这也可以帮助我们管理不同的版本。Build TypeProduct Flavors总是联合在一起的,它两结合的结果就称之为Build Variant

Build Types

在Gradle的Android Plugin中,Build Type用于定义App以及Library如何构建。每一个Build Type都会指明是否为Debug,Application Id,是否无用的资源应该被删除掉等等。你也可以在buildTypes的代码块中定义多种Build Types。Android Studio默认生成的标准的build Types代码块如下:

android {
       buildTypes {
           release {
               minifyEnabled false
               proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
           }
} }

一个新的Module默认的build.gradle文件中会配置一个release的Build Type。这个build type通过设置minifyEnabled为false禁用删除无用的Resources,以及定义了默认的ProGuard配置文件。

创建Project的时候不仅仅只有Release的构建类型,默认每个Module都有一个Debug的构建类型。我们可以在里面改改里面的值。

创建Build Type

当默认的配置不满足需求时,我们可以创建我们自定义的Build Type。我们需要做的就是在buildTypes代码块中创建一个新的对象即可,如下所示,创建一个名为staging的Build Type:

android {
    buildTypes {
        staging {
            applicationIdSuffix ".staging"
            versionNameSuffix "-staging"
            buildConfigField "String", "API_URL","\"http://staging.example.com/api\""
        }
    }
}

staging的Build Type定义了一些Application Id的后缀,使得Application的ID与Debug/Release版本不一样。假设你已经有了默认的Build配置,这些版本的ApplicationId会如下:

  • Debug: com.package
  • Release: com.package
  • Staging: com.package.staging

这也就意味着我们能够在同一台设备上安装多个版本的。也可以使用buildConfigField属性定义了不同的URL。

我们也可以通过Copy其他Build Type中的属性,来初始化一个新的BuildType,通过initWith来初始化该BuildType对象。代码如下所示:

android {
       buildTypes {
           staging.initWith(buildTypes.debug)
           staging {
               applicationIdSuffix ".staging"
               versionNameSuffix "-staging"
               debuggable = false
            } 
      }
}

initWith方法创建了一个新的Build Type,并且从一个已经存在的build type中复制这些属性。它也可以重写这些属性,或者定义其他的新的属性。

Source sets

当创建了一个新的build type之后,Gradle也会创建一个新的source set。默认的source set目录会放在相同的Build Type的目录下。当你创建一个新的build type时,该目录不会自动创建,你必须在你使用代码与资源前自己为每一个build type创建source set目录。

这是标准的目录结构,包含了三种Build Type:

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

比如,如果希望在某个build type下替换一些属性,添加一些代码,或者添加一些layouts、strings的话,都是可以做到的。

当使用不同的source sets的时候,Resources会比较特殊。Drawables和layout文件都会被在Main Source Set中的相同名字的资源所重写,但是在values文件夹下面的,如strings、colors、dimens等则不会。Gradle会用main resources来merge各个build type的资源。

例如,如果有一个strings.xml文件在main source set中:

<resources>
       <string name="app_name">TypesAndFlavors</string>
       <string name="hello_world">Hello world!</string>
</resources>

如果在staging的build type中也存在一个strings.xml:

<resources>
       <string name="app_name">TypesAndFlavors STAGING</string>
</resources>

那么最后merge完的strings.xml会如下:

<resources>
       <string name="app_name">TypesAndFlavors STAGING</string>
       <string name="hello_world">Hello world!</string>
</resources>

同样AndroidManifest.xml也是同样的,特定的build type的包会把main source set中的AndroidManifest.xml覆盖。

Product flavors

Build Type可以对于相同的App配置生成不同类型的构建,与Build Type相反,product flavors用来创建相同的App,但是不同的版本。典型的例子就是App有免费和付费版本。另外一个常用就是为只有一个品牌但是有很多客户端,比如说滴滴,外卖,银行等都有司机端和用户端。他们只想修改Logo,Color,Url等等。Product Flavors可以很简单的处理相同的代码生产出不同的版本。

如果你不确定是否需要一个新的build type,或者新的product flavor,那么则需要看一下是否真的需要构建一个新的APP发布到应用市场上。

创建Product Flavors

我们可以通过添加productFlavor代码块来添加一个新的Product Flavor:

android {
       productFlavors {
           red {
               applicationId 'com.gradleforandroid.red'
               versionCode 3
            }
          blue {
            applicationId 'com.gradleforandroid.blue'
            minSdkVersion 14
            versionCode 4
           } 
     }
}

Product Flavors拥有和Build Type不同的属性。因为Product Flavors是一个ProductFlavor类,就像defaultConfig对象一样。这也就意味着,defaultConfig和所有的Product flavors共享相同的Properties。

Source Set

就像Build Types一样,Product Flavors能够拥有他们自己的Source Sets目录。创建一个与Product Flavors名字相同的文件夹。而这个目录的明哲,需要联合它的Build Type以及Flavors,这样用来覆盖那些属性。

比如,你想有一个不同的App Icon在blue flavors中生成一个Release版本的包,那么这个目录应该叫做blueRelease。然后这个组件所关联的目录将会比其他Build Type以及Product Flavors的组件目录优先级会更高。

Multiflavor variants

在某些情况下,你可能希望创建一些联合的Product Flavors。比如说,Client A和Client B都基于相同的代码需要一个免费和付费的版本。创建四个不同的Flavors单独的Settings是不可行的。所以,Combining Flavors可以更高效的使用flavor dimensions

 android {
       flavorDimensions "color", "price"
       productFlavors {
           red {
               flavorDimension "color"
           }
            blue {
               flavorDimension "color"
           }
          free {
               flavorDimension "price"
           }
           paid {
               flavorDimension "price"
          } 
     }
}

当添加了flavor dimensions之后,Gradle希望你为每个Flavor都指定一个flavor dimension。如果你忘记了,则编译时会报错。flavorDimensions数组定义了这些Dimensions,而这些Dimensions的顺序是非常重要的。当需要联合两个Flavors的时候,你可能已经定义了相同的Properties或者Resources。在这种情况下,flavors dimensions数组的顺序决定了哪个flavor配置会覆盖另外的。在之前的例子中,Color Dimension会覆盖Price Dimension 。并且这个顺序,也决定了构建的名字。

假设默认的构建配置有Debug和Release两种Build Type,就像之前的Example中定义的flavors就会生成以下这些版本:

  • blueFreeDebug and blueFreeRelease
  • bluePaidDebug and bluePaidRelease
  • redFreeDebug and redFreeRelease
  • redPaidDebug and redPaidRelease

Build variants

Build Variants仅仅只是Build Types以及Product Flavors的联合。一旦创建了一个新的Build Type或者Product Flavor的话,那么一个新的Variants就会被创建。

例如,如果有一个标准的Debug和Release构建类型,并且你创建了一个Red和Blue的Product Flavor,那么下面的Build Variant就会生成:

Build Variants

这是Android Studio中的一个窗口。可以在tool window的左下角找到它,或者从View->Tool Windows->Build Variants中打开。我们也可以选择其中的Variant来执行任务。如果没有定义任何的Build Types的话,Android Plugin会默认创建一个Debug的Build Type。

Tasks

Android Plugin会为每一个配置的Build Variant创建Tasks。一个新的Android App拥有Debug和Release两种Build Types,所以默认的就会有两个Task,一个是assembleDebug一个是assembleRelease来构建不同的APK。当添加一个新的Build Type的时候,一个新的Task也就会被创建,一旦你开始添加Flavors,一整套Tasks就会被创建,因为每一个BuildType的Tasks都会为每个Product Flavor联合。也就是,一个简单的Build Type和Flavor设置后,就会有三个任务去构建所有的Variants。

  • assembleBlue:使用blue flavor配置并且assemble BlueRelease和BlueDebug
  • assembleDebug:使用Debug Build Type的配置,并且为每一个Product Flavor assemble一个Debug的版本
  • assembleBlueDebug:combines特定的Flavor以及BuildType配置,并且Flavor的设置会覆盖BuildType的设置

每一个BuildType和Product Flavor都会创建新的Tasks。

Source Set

Build Variants是一个联合了BuildType和ProductFlavors并且使用它们自己SourceSet目录的版本。

例如:Variant创建从Debug Build Type以及Blue、Free Flavor的版本,它可以拥有src/blueFreeDebug/java/的Source Set。我们可以在sourceSets代码块中重写它的location。

Resource and manifest merging

Android Plugin需要在打包前对Main的SourceSet以及BuildType的SourceSet进行一次Merge。而且Library工程也会提供额外的资源,它们也会被Merge,例如Manifest.xml等等。也会在其中声明一些权限等。

Resource和Manifest.xml的优先级顺序如下:

Order.png

如果一个Resource声明在Flavor和Main source set中的话,那么Flavor中的值优先级会更高。在这种情况下,Flavor的SourceSet中的资源会被打包到APK中。而Library工程的资源优先级会是最低的。

Creating build variants

Gradle可以很容易的处理复杂的多种构建。甚至当创建两种BuildType和两种Product Flavors的时候。例如:

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"
            } 
      }
}

在这个例子中,我们会创建出来四个不同版本的Variants:
blueDebugblueStagingredDebugredStaging
每一个都有API_URL以及flavor_color的属性。

以下为blueDebug的样式:

blueDebug

而以下为redStaging的样式:

redStaging

Variant filters

通过Variant fileters的方式,可以完全忽略某种Variant的构建,从而达到使用assemble命令的时候提升构建的速度。并且不会执行的Task也不会打印的Tasks列表中出现。这样也同样会确保build variant不会在Android Studio中显示。

我们可以通过在App或者Library的Root-Level的build.gradle文件中添加以下代码:

android.variantFilter { variant ->
       if(variant.buildType.name.equals('release')) {
           variant.getFlavors().each() { flavor ->
               if (flavor.name.equals('blue')) {
                  variant.setIgnore(true);
            }
       } 
    }
}

在这个例子中,首先检查BuildType是否为Release,然后检查Flavors的名字,如果flavors为blue则忽略。其中variant.getFlovors会获取到flavor dimensions中所有的flavor。

variant filter

可以看到blueFreeReleasebluePaidRelease已经不在列表中。如果直接执行gradlew tasks的话,就会注意到所有和这个variants相关的tasks都不存在了。

Signing configurations

在发布App到Google Play或者其他的商店的时候,我们需要使用一个Private Key对APK进行签名。如果有一个付费和免费的版本,或者不同的客户端版本时,你需要为不同的Flavor版本APK进行不同的签名。

android {
       signingConfigs {
           staging.initWith(signingConfigs.debug)
           release {
               storeFile file("release.keystore")
               storePassword"secretpassword"
               keyAlias "gradleforandroid"
               keyPassword "secretpassword"
        } 
    }
}

在这个例子中,我们创建了两个不同的签名。
debug配置会被Android Plugin自动设置,并且使用一个已知的Password进行签名,所以不需要为Debug的BuildType创建签名配置。而staging配置使用initWith,它是从另外一个签名配置中Copy的属性。这也就意味着staging的构建会和Debug一样的签名,而没有它自己定义的签名。
release配置则使用storeFile来指定keystore文件,并且定义了Key的别名以及Password。

当定义完了这个签名的配置后,你需要在BuildType或者Flavors中应用一下。BuildType和Flavors都有一个属性叫做signingConfig,如下所示:

android {
       buildTypes {
           release {
               signingConfig signingConfigs.release
            } 
       }
      productFlavors {
           blue {
               signingConfig signingConfigs.release
          } 
      }
}

通过这种方式会对BuildType以及ProductFlavors应用不同的签名。

当签名一个Flavor版本的时候,你需要重写BuildType中的签名配置W。当需要使用相同的BuildType不同版本的Flavors的签名时,可以通过下述方式:

android {
       buildTypes {
           release {
               productFlavors.red.signingConfig signingConfigs.red
               productFlavors.blue.signingConfig signingConfigs.blue
           } 
       }
}

上面这个例子展示了如何在redblue的Release版本使用不同的签名,但是却不影响Debug和Staging的BuildType。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容