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。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏小筱月

java 开发 websocket 网页端聊天室

WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。

62120
来自专栏牛肉圆粉不加葱

Spark Storage ② - BlockManager 的创建与注册

上一篇文章介绍了 Spark Storage 模块的整体架构,本文将着手介绍在 Storeage Master 和 Slave 上发挥重要作用的 BlockMa...

7010
来自专栏向治洪

Android资源混淆打包方案

概述 我们知道在Android的打包过程中,有一个步骤是压缩,也是为了减少apk包的大小,其中在压缩的过程中,很大一部分就是对资源的压缩,除了系统的压缩方案之外...

40270
来自专栏小白安全

Janus高危漏洞深度分析

一、背景介绍 近日,Android平台被爆出“核弹级”漏洞Janus(CVE-2017-13156),该漏洞允许攻击者任意修改Android应用中的代码...

36190
来自专栏向治洪

android打包方法超过65k错误

近日,Android Developers在Google+上宣布了新的Multidex支持库,为方法总数超过65K的Android应用提供了官方支持。 如果...

18450
来自专栏菜鸟程序员

Janus高危漏洞深度分析

17530
来自专栏高性能服务器开发

(三)服务器端的程序架构介绍1

通过上一节的编译与部署,我们会得到TeamTalk服务器端以下部署程序: db_proxy_server file_server http_msg_server...

38870
来自专栏三丰SanFeng

Linux进程间通信(一) - 管道

管道(pipe) 普通的Linux shell都允许重定向,而重定向使用的就是管道。 例如:ps | grep vsftpd .管道是单向的、先进先出的、无结构...

26470
来自专栏编程

修复 Linux/Unix/OS X/BSD 系统控制台上的显示乱码

有时我的探索会在屏幕上输出一些奇怪的东西。比如,有一次我不小心用 cat 命令查看了一下二进制文件的内容 —— cat /sbin/*。这种情况下你将无法再访问...

22360
来自专栏转载gongluck的CSDN博客

打开文件open()函数的使用方法详解

头文件:#include <sys/types.h>    #include <sys/stat.h>    #include <fcntl.h> 定义函数...

31250

扫码关注云+社区

领取腾讯云代金券