使用gradle打包多个变体(variant)

转载请标明原文地址:http://www.jianshu.com/p/843055bf6edd

重要更新

  1. 现在新建变体还需要在 defaultConfig 下加入 flavorDimensions "versionCode"
  2. 我写了一个脚本可以快速的创建变体的sourceSet,项目地址:junerver/flavor_cli

使用方法:将该脚本复制到项目根目录下(与app目录同级),然后运行指令:python3 flavor_variant.py,后续按照提示输入即可,因为是供自己开发快速使用的,所有必然有bug与一些严格匹配的要求,欢迎提PR!


Gradle

背景:刚刚接手的项目中包含3个客户端app(两个eclipse工程、一个AS工程),同时这个项目根据不同用户的制定还有两个衍生版本。原来的开发人员将项目复制后修改,在我接手时一共存在着9个工程文件。

当我看到这个项目的时候近乎崩溃,因为这意味着每修改一个端的内容还要记着同步到其他的两个端中。查看后发现,衍生版本中大量的文件是重复的,只是部分比如资源文件、后台接口地址等是不同的。我便开始思考,如何通过一个Android Studio工程同时实现修改这三个版本。

于是便想到了曾看过stormzhang写过的ANDROID STUDIO系列教程六–GRADLE多渠道打包,这篇文章中简单描述了如何使用gradle进行多渠道打包(使用占位符替换AndroidManifest文件中友盟统计的UMENG_CHANNEL的值)。

这给了我一定的思路,通过查阅资料,确定了可以使用gradle打包出不同变体(不同applicationId、不同资源文件、不同APP名称与图标、不同Java文件)。

长篇讲解可以查看文章末尾的参考阅读,本文只介绍如何实现。


1 配置不同变体的属性(签名、applicationId)

由于项目中每个客户端都相当于存在三个版本,使用不同的签名文件,因此需要首先实现的就是对不同的变体配置不同的签名!

配置签名文件

如上图所示,选择app module,选择Signing选项卡,首先配置好了这三个变体版本的签名文件。

配置变体Flavors

在Flavors选项卡中,新建我们的变体(variant),并对变体进行配置!所有的Flavor都会复写defaultConfig中的属性,所以可以看到我并没有填写其中的一些属性。在这里还可以对不同的变体设置不同的applicationId(重要!这与极光推送等第三方SDK有关)。

创建完毕后同步项目,会发现app moudle下的build.gradle文件内容也发生了变化,如下图所示:
Paste_Image.png

如果你可以熟练的使用gradle也可以选择不使用AS提供的UI界面,直接编写gradle文件。

2 新建不同变体的sourceSet

在修改完变体的配置文件后,我们还需要再项目的src文件夹下新建以我们的Flavor名称命名的文件夹,并在这些文件夹下新建如main中相同的目录结构。
目录结构

我们正常编写项目都是写在main这个sourceSet下的,但是如果我们的项目的变体有不同的资源文件、Java文件时,我们就需要使用不同的sourceSet来区别开。

需要注意的是,如果是资源文件,Flavor下的资源文件会与main中的合并,如果存在重复,则Flavor中优先级高于main中。我们可以将不同变体中共用的资源存放在main中,只将不同的内容存放在flavor的sourceSet中。

如果不同变体有内容不同的Java文件则要注意,需要将这个 Java 文件放置到每个 flavor 的 sourceSet 文件夹下,main中不可以有这个Java文件,如果main中也存在此文件,编译时会提示文件重复。比如说有两个变体,有着不同的 MainActivity.java,那么 main 中就不能有这个文件了。需要把这个 Java 文件放到各个 flavor 的 sourceSet 下,同时这个 Java 文件在 sourceSet 中要按照 main 中的包结构保存。

3 AndroidManifest占位符

由于不同的项目有不同的名称、图标,这一点我们可以通过类似上一步的方法,在不同的sourceSet中配置string.xml中的app_name属性,与drawable文件夹中的ic_launcher。但是这样有些麻烦,当我们的变体版本多了得手就需要不断的重复这一动作,所以我使用的是在AndroidManifest文件中使用占位符然后在flavor中直接配置的方法。这样做的好处是,如果以后图标变更只需要到main中找到该文件然后替换即可,而不用去一个个找sourceSet。

首先将所有图标文件放到main中,然后在 AndroidManifest中使用¥{NAME}格式的占位符,最后在flavor中使用manifestPlaceholders =[NAME1:VALUE1,NAME2:VALUE2]替换占位符中的内容。

AndroidManifest占位符

在项目中,每个衍生版本都有自己的极光推送APPKEY属性,这也可以是用占位符这一方法来处理,最终的flavors如下图所示:

gradle文件

4 调试不同版本

现在我们拥有了三个不同的变体,但是我们调试的时候如何选择对应的程序来调试呢?

方式一:
方式一

方式二:
方式二

在选择好要调试的变体后,会发现对应的 sourceSet 文件夹变成了我们工程文件夹的央视:

选择需要调试的变体

可以看到我们一共有6个可选变体,这是怎么回事呢?我们明明只设置了3个flavor。

这时就需要介绍buildTypes了,再次回到项目配置页面如下图所示:

buildTypes

可以看到有两个build type,这其中可以配置构建的一些选项,这里就不做过多介绍了。

我们的总变体数量等于 (build type数量)*(flavor数量),这就是为什么一共有六个可选的variant。

最后献上一个release的buildType配置

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
release {
minifyEnabled true
shrinkResources true //移除无用资源
debuggable false
zipAlignEnabled true //Zipalign优化
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.uerbT
// 自定义输出配置
applicationVariants.all { variant ->
variant.outputs.each { output ->
def outputFile = output.outputFile
if (outputFile != null && outputFile.name.endsWith('.apk')) {
// 输出apk名称为UerbT_v1.0_2016-12-01_uerbt.apk
def fileName = "UerbT_v${variant.versionName}_${releaseTime()}_${variant.flavorName}.apk"
output.outputFile = new File(outputFile.parent, fileName)
}
}
//过滤掉unaligned的包
variant.assemble.doLast {
variant.outputs.each { output ->
println "-----------------------------------------"
println "aligned " + output.outputFile
println "unaligned " + output.packageApplication.outputFile
File unaligned = output.packageApplication.outputFile;
File aligned = output.outputFile
if (!unaligned.getName().equalsIgnoreCase(aligned.getName())) {
println "deleting " + unaligned.getName()
unaligned.delete()
}
}
}
}
}

releaseTime() 函数如下:

1
2
3
def releaseTime() {
return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC"))
}

参考阅读:
重要-Gradle for Android 第四篇( 构建变体 )
重要 - 使用gradle构建不同特性的app
gradle配置详解
知乎问答-如何使用gradle构建不同的app
android studio gradle 多版本多apk打包
使用Gradle自动化构建多类型apk包
外包采用Gradle生成多套app打包
ANDROID STUDIO系列教程六–GRADLE多渠道打包
Android Studio中Gradle使用详解