Gradle Plugin Samples 之 Gradle Build Variants(六)

Gradle Build Variants

本例用于讲解如何使用 Gradle 利用一份代码生成多种 APK 。

本例中, app 文件夹中,除了默认会生成的 main 目录以及 androidTest 目录之外,我额外添加了6个目录,分别是: releasedebugbuildtypesnochangeplaystoreamazonstoreproductflavorsnochange 。同时,我们在 app/build.gradle 中将这 6 个文件夹分不到 buildTypesproductFlavors 中。

buildTypes {
release {
    applicationIdSuffix '.release'
    signingConfig signingConfigs.release
    zipAlignEnabled false
}
debug {
    applicationIdSuffix '.debug'
    zipAlignEnabled false
}
buildtypesnochange {
    signingConfig signingConfigs.release
    zipAlignEnabled false
}
}

productFlavors {
playstore {
    applicationId 'cc.bb.aa.gradle_build_variants.playstore'
}
amazonstore {
    applicationId 'cc.bb.aa.gradle_build_variants.amazonstore'
}
productflavorsnochange {}
}


我们通过 gradle build 命令,可以生成 9 种不同的 APK ,见下图:



这 9 种 APK 的包名不同,图标不同,应用程序名称不同,首页中显示的内容不同,但是却来自一份代码。

设置所有的 buildTypeszipAlignEnabledfalse 目的是为了只生成 unaligned 的 APK ,用于只产生 9 个 APK 。因为 unaligned 的 APK 是编译 aligned 的 APK的中间产物,会影响我们最终编译出的 APK 的个数。

releasebuildtypesnochange 设置签名是为了方便安装到设备中。
仔细观察额外添加的这 6 个目录的结构,可以发现它们的结构和 main 的结构是相同的。但是,它们和 main 中的文件还是有一些区别的:
1. 我在对应的目录下放置了名称相同内容却不同的 ic_launcher.png 图片。
2. 在 releasedebugplaystoreamazonstore 中修改了 strings.xml 的内容,同时删除了 action_settings 字段。
3. 在这 6 个目录中各自创建了对应的类,releasedebugbuildtypesnochange 中创建的类类名和方法名均相同,只是返回结果不同; playstoreamazonstoreproductflavorsnochange 中创建的类类名和方法名均相同,只是返回结果不同。
4. 这6个文件夹的 res 文件中的文件以及文件夹,相对于 main/res 文件夹很多都没有。

观察 Android Studio 中 Build Variants 面板,发现 app 的选项列表已经不是默认的 releasedebug 了。



这些列表是一个 productFlavors 和一个 buildTypes 组装的结果。

在 Android Studio 的 Gradle Plugin 中,每一个 APK 均是由一个 buildTypes 和一个 productFlavors 组装而成。

在默认的情况下, buildTypesreleasedebug 两个分支; productFlavors 没有。

每一个 module/src 都有一个名称为 main 的文件夹。这个文件夹属于 buildTypesproductFlavors 基础, buildTypesproductFlavors 都可以访问和修改 main 文件夹中的内容。

例如:

debug 类型的 APK 的名称为 Debugrelease 类型的 APK 的名称为 Releasebuildtypesnochange 类型的 APK 的名称为 playstoreamazonstoreproductflavorsnochange 中设置的 apname 名称(分别对应 PlayAmazonGradle-Build-Variantsbuildtypesnochangeproductflavorsnochange 中没有设置 appname ,则使用了 main 中的 appname)。

debug 类型的 APK 的图标为 Drelease 类型的 APK 的图标为 Rbuildtypesnochange 类型的 APK 的图标为 playstoreamazonstoreproductflavorsnochange 中设置的 apname 图标(分别对应图标 PA 、Android 默认图标。 buildtypesnochangeproductflavorsnochange 中均没有设置 ic_launcher.png ,则使用了 main 中的 ic_launcher.png)。

在类 MainActivity 中,有这么一段代码:

TextView textView = (TextView) findViewById(R.id.textview);
textView.append("\nappName = " + getString(R.string.app_name));
textView.append("\nBuildTypesName = " + BuildTypesUtils.getBuildTypesName());
textView.append("\nProductFlavorsName = " + ProductFlavorsUtils.getProductFlavorsName());
textView.append("\npackageName = " + getPackageName());


实际上,在 main 文件夹中,并没有定义 BuildTypesUtils 类和 ProductFlavorsUtils 类( BuildTypesUtils 类定义在 releasedebugbuildtypesnochange 中; ProductFlavorsUtils 类定义在 playstoreamazonstoreproductflavorsnochange 中),但是我们可以使用这些类。

---

当你在 Android Studio 的 Build Variants 面板中切换当前选择的 Build Variants ,你会发现在 Project 面板中,对应的两个文件夹的 javares 文件夹的图标会发生变化(显示为资源文件夹图标的样式),而 main 文件夹中的这两个文件夹一直表现为资源文件夹图标的样式。

你在 Build Variants 面板切换 Build Variants ,实际上是在更改当前编译的分支。当你选择了一个 Build Variants 后,Android Studio 会编译改 Build Variants 对应的 buildTypesproductFlavors 中的类以及资源文件,重新组装,形成新的 App 。所谓的重新组装,简单理解起来就是,将当前的 Build Variants 对应的 buildTypes 文件夹中的内容、当前的 Build Variants 对应的 productFlavors 对应的文件夹中的内容、 main 文件夹中的内容合并到一起,形成一个并集。

合并规则:
  1. 图片、音频、 XML 类型的 Drawable 等资源文件,将会进行文件级的覆盖(本例中的 ic_launcher.png)。
  2. 字符串、颜色值、整型等资源以及 AndroidManifest.xml ,将会进行元素级的覆盖(本例中的 appnamehello_world)。
  3. 代码资源,同一个类, buildTypesproductFlavorsmain 中只能存在一次,否则会有类重复的错误(这就是为什么本例中没有在 main 中定义 BuildTypesUtils 类和 ProductFlavorsUtils 类)。
  4. 覆盖等级为:buildTypes > productFlavors > main (这就是为什么 release 类型的 APK 的名称都是 Releasedebug 类型的 APK 的名称都是 Debugbuildtypesnochange 类型的 APK 的名称需要根据 productFlavors 来确定)。

本项目样例源码请到我的 github 或者 oschina 上查看(文件过大,上传附件失败)。
4 分享
studio zed hongfeiliuxing zola
帝都雾霾

帝都雾霾

您github上该sample的地址可以分享下吗
0 赞 2015-01-09 13:22
monroe

monroe 回复 帝都雾霾

在这个系列的第一篇文章里有的。https://github/yanglw/Android-Gradle-Examples
0 赞 2015-01-09 22:45
monroe

monroe 回复 帝都雾霾

在这个系列的第一篇文章里有的。https://github.com/yanglw/Android-Gradle-Examples
0 赞 2015-01-09 22:53
wx1985113

wx1985113

您好,看了您的文章,非常有帮助,我想请教一个问题,如果是eclipse兼容模式的项目,怎么配置这种打包方式呢?
0 赞 2015-03-09 17:30
wx1985113

wx1985113

刚刚研究了一下,我已经知道了,通过release.java.srcDirs= 这种方式去指定目录,但是如果这样做了,这个项目好像就不能用eclipse编译了,因为在release目录下写的文件不能跟mian下面的文件重复
0 赞 2015-03-09 17:54
monroe

monroe 回复 wx1985113

这个是因为,这种多分支的编程,是需要 gradle 支持的。eclipse 默认使用 ant 构建项目,所以是不可以的。你可以尝试在 eclipse 中安装 gradle 插件,使用 gradle 构建项目。
0 赞 2015-03-10 11:10
wx1985113

wx1985113 回复 monroe

感谢你的回复,我在想为什么drawable目录下的图片资源可以自动覆盖,但是java文件却覆盖不了。
0 赞 2015-03-10 14:43
monroe

monroe 回复 wx1985113

这个,我确实不清楚。。。。其实,这和版本控制有点像。在版本控制中,图片文件变化时,一般都是直接显示文件变化;代码变化时,会显示哪些行发生了变化。
0 赞 2015-03-10 22:18
wx1985113

wx1985113 回复 monroe

非常感谢你的回复
0 赞 2015-03-11 18:06
aslover

aslover

感谢楼主,楼主好人
0 赞 2015-10-30 10:54
zzzhouzhong

zzzhouzhong 回复 monroe

因为一个是二进制文件一个是文本文件。文本文件可以分析行,二进制文件不可以。类似的linux和git的diff命令也只能分析文本文件
0 赞 2015-12-23 20:54

要回复文章请先登录注册