当我们在开发一款应用时,通常会面临发布不同的版本需求。举两个常见的场景,场景一:我们正在开发新功能,开发完成后需要发布提测版本提交给QA测试人员,测试通过后再发布线上版本,这时线下版本和测试版本的服务器接口域名不一样又或者有不同的api接口;场景二:我们的app需要发布一个免费版本和付费版本,付费版本会有更高的使用权限。针对如上两种情况我们就需要发布四个apk,免费QA版,免费线上版,付费QA版,付费线上版,如果在代码里面硬编码会使得项目异常复杂。gradle针对这种情况,提出了解决这种问题的方法,对于QA版或线上版可以配置build type,Androidstudio默认配置了Debug和Release两种type,对于付费版或者免费版,可以通过配置Build flavors。这两种类型组合起来就叫做build variant
Build types
Gradle在Android中的build type是用来处理app或者library应该被构建成什么类型,在这个配置中,我们可以定义应用的包名是什么,是否自动去除掉没有引用的资源,是否开启混淆等等。具体配置可参考如下代码
1 | android { |
当我们新建一个项目或者模块时,build.gradle文件会默认配置上release的build type,默认配置关闭了混淆功能(即minifyEnabled设置为false)和定义混淆文件的位置。
除了release build type再就是debug build type也是默认创建好的,但是没有在代码里显示出来,如果你想修改默认Debug中的配置,需要单独声明出来。
debug的build type默认设置debug属性为true,方便调试
创建build types
当我们觉得默认的两种类型不够用时,我们能很容易的自定义build type,如下代码展示了创建一个新的build type名叫qa_test的类型
1 | android { |
上面的代码主要配置qa_test的类型功能为:给包名加上了.qatest的后缀,这样我们能在手机上安装相同的程序,因为应用包名不一样;在version name上加上了-qatest的后缀,然后再BuildConfig类中加了一个API_URL的string属性。
通过buildConfigField配置,会在编译后BuildConfig类中增加对应定义的常量
在自定义build type是,还可以重用已用的build type,如下代码所示
1 | android { |
**initWith()**方法为qa_test类型拷贝了debug类型的所有属性,但是我们也可以修改其中的属性,通过显示声明出来。
build types代码目录结构设置
当我们创建一个新的build type,gradle也会默认指定一个同名的目录在项目里面,但是不会把目录自动创建出来,所以需要我们手动创建一个同名的目录,其结构如下所示
这种配置可以使得我们在任意build类型去自定义修改代码,比方说在release的类型登录界面带上正式版本的Logo,在debug版本的登录界面就带上测试版本的Logo
Tips:当我们创建了不同的build type,并在不同的type文件目录里面做不同的修改,但使用的源是基于main目录下的代码,比方说我们要在不同的type里面有不同的login界面,那么main包下面就不能包括LoginActivity,而只在不同buildtype下加入各自自定义的LoginActivity,如果我们也在main目录下加入,编译器会提示dumpllicated file的错误
对于Resources资源,和处理Java代码有些许不同,对于layout、Drawable资源和图片,如果在不同的type里面定义了,那个定义的这些文件会直接替换掉main目录里面相同的资源文件;而对于String,Color的资源则会采取合并的策略,举个例子,如我们在main下的string.xml如下
1 | <resources> |
如果qa_test type中的string.xml如下
1 | <resources> |
合并后的string.xml如下所示
1 | <resources> |
如果我们没有自定义string资源文件,那gradle就会默认使用main目录下的string。对于AndroidManifest.xml文件也是一样的合并策略,如果要修改manifest文件,只需要在对应的build type目录中的manifest文件加入我们需要的代码,gradle会自动合并,在后续部分会更深入的介绍gradle合并的原理
依赖
每一个build type有可能有自己独立的依赖,如果我们配置了,gradle会自动识别配置,例如要在debug type中加入一个日志框架,可以参考如下代码
1 | dependencies { |
debugCompile表示只在debug type中把log4j加入到编译路径中,关于常用的集中依赖scope请参考系列三:Gradle依赖管理
Product flavors
与build type相反,build type用来构建不同类型的app,debug版或release版,而produc flavors则是用来在同一个app上创建不同的版本,如付费版和免费版。一个非常常见的应用场景就是我们创建了一个银行管理的app给不同银行提供服务,但是不同的银行App的logo不一样,有produc flavors就能在基于一套代码上创建不同版本的app或者library。
如果我们不确定什么时候用build type,什么时候该用product flavors,那么问自己几个问题,如果是构建不同类型供内部使用或者是在google palay上提交一个新的app,建议使用build type;如果app要分发在不同的渠道上,那么建议product flavors
创建 product flavors
创建product flavors与build types非常相似,你只需要添加productFlavor代码块,如下代码所示
1 | android { |
Product flavors与build types属性不同,因为product flavors其实是ProductFlavors类,build.gradle文件中默认添加的defaultConfig也是ProductFlavors类的实例。
product flavors代码目录结构设置
与build type的设置类似,product flavors也可以配置自己的代码目录,可参考Build Type目录结构设置,这里不再赘述。
Multiflavor variants
在某些情况下,我们可能需要进行flavors的组合,比方说你的app有两套主题,绿色主题和红色主题,然后有两个版本,付费版和免费版,你可能需要进行组合,类似于红色免费版,红色付费版,绿色免费版,绿色付费版。通过使用flavorDimensions可以解决flavors组合的问题,配置可参考如下代码
1 | android { |
当我们添加了flavor dimension之后,gradle会判断我们是否为flavor指定了flavorDimensions中设定的值,如果没有设置,gradle编译的时候就会报错,而且flavorDimensions中设置的顺序也是非常重要的,比方说如果红色主题的flavor和付费版的flavor中某处代码修改是一样的,那么在运行的时候就以flavorDimensions中配置的先后顺序为准,根据上面的配置,就是以红色主题中的代码为准。
假设我们的build type的类型是配置的debug和release那么根据上面代码的配置生成的build variants为如下
- blueFreeDebug 和 blueFreeRelease
- bluePaidDebug 和 bluePaidRelease
- redFreeDebug 和 redFreeRelease
- redPaidDebug 和 redPaidRelease
Build variants
build variants就是build type和product flavors组合的结果,当我们创建一个新的build type或者product flavor的时候,新的variants也就相应的创建了。例如,如果我们的build type是标准的debug和release,那么创建一个product flavor为red theme和blue theme的时候,相对应的build variants为自动生成。
如上截图为Android Studio中Build Variants的工具框,默认在Android Studio界面的左下角边缘倒数第二个,亦或可以通过View | Tool Windows | Build Variants打开。
如果我们没有配置product flavors,variants只会包括build types。如过我们也没有配置过build type,Gradle的Android插件也会默认配置debug build type,所以build variants不会出现为空的情况。
Tasks
Gradle的Android插件会根据我们配置的build variant自动生成task。一个新的Android app项目会默认有debug和release两种build type,所以我们可以运行assembleDebug和assembleRelease去生成不同apk,或者直接运行assemble来生成两种build type的apk。当我们新添加build type,就会相应的添加新的task。当添加新的flavors product,task重新生成,变化会比较大,因为每一个product flavor和build type是组合的。所以即便是最简单的一个build type和一个flavor的组合,都会生成有三个对应的task:
assembleBlue 使用Blue flavor,相当于运行assembleBlueRelease和assembleBlueDebug
代码目录配置(Source sets)
Build variants就是build type和product flavor的组合,比如我们源代码中有main,releas,debug,red四个目录,其中,release和debug是build type的类别,red是设置的productflavor,那个对应的build variant就是redReleas,redDebug,也就是说redReleas使用的源码是red和release目录的合并,同理redDebug使用的是red和debug目录中代码的合并。
资源文件和manifest文件的合并
不同的build type和product flavor有不同的源码目录,在生成不同的build variant包时,会需要合并资源,例如我们在debug build type中的manifest文件设置了要存储log日志的权限,但是在main目录中的manifest文件却不需要,这样在生成编译debug build variant的时候就需要合并main目录中的manifest文件和debug目录中的manifest文件。其中选取的资源的优先级如下图所示:
如上可知,如果flavor中有一个图片资源为logo.png,main目录中也有一个图片资源为logo.png。在打包的时候,因为flavor的优先级大于Main所以,会使用flavor中的logo.png
关于资源和manifest文件的合并,有很多具体的细节在这里没法阐述,官方文档给了更多更详细的解释,可以访问这个地址 [Android Developer:Manifest-Merger](http://tools.android.com/tech-docs/new-build- system/user-guide/manifest-merger)
创建build variants
gradle使得处理复杂的build varinats非常容易,即使我们创建了两种build type和两种product flavors。build文件中代码还是非常清晰明了
1 | android { |
在上面的代码中,我们创建了四种build variants:blueDebug,blueStaging,redDebug,redStaging。每一个variant都自定义了API URL 和flavor color。运行程序blueDebug可能为如下所示
而redStaging可能如下图所示
第一个截图是blueDebug,使用的是debug build type中的URL和blue product flavor中的颜色,同理可类推redStaging
Variant filters
在某些情况下,可能我们不想使用某种build variant,例如现在有debug和release的build type,red和blue的flavors,但是,blue还是测试环境根本现在用不到blue的release版本,那么我们可以直接过滤掉,在android studio中的buildVariants窗口就不会出现了,过滤可以在app模块或者library模块的build.gradle文件中加入如下代码:
1 | android.variantFilter { variant -> |
加入以上代码后,再同步项目,在AndroidStudio的BuildVariants窗口就可以看见没有了blue release的版本了
Signing Configuration(apk签名配置)
在将app发布到Google Play或者其他应用市场之前,我们需要对apk进行秘钥签名。如果我们针对用户有免费和付费两个版本,那么需要给每一个flavor不同的签名,签名的配置代码参考如下
1 | android { |
在上面的例子里,我们创建了两种不同签名配置
debug类型的签名配置是由gradle的android插件自动生成的,密码的alias name都是默认的。
Tips:密码建议不要写在build.gradle文件中,可以写在local.properties文件中
加入了签名配置后,就可以使用签名信息了,代码可参考如下
在buildType中使用
1 | android { |
在flavors中使用
1 | android { |
由上代码可知,我们不能如下配置
1 | android { |
因为在合并build type和flavor时,flavor会覆盖type中的签名
总结
在这一部分,主要讨论了build type,produc flavors,和两者的组合以及组合后源码,资源文件的一些细节处理;再就是介绍了签名的配置。