Gradle是Android studio用到的一个自动构建系统,基于Groovy语法,用来管理和构建Android项目,它可以精细的处理构建过程的各个步骤和简化持续集成(CI),总之就一句话,Gradle的灵活超乎您的想象
名词解释
manifestPlaceholders:占位符,用于替换Androidmanifest文件中${VALUE}标签对应的值,多用于多渠道打包
buildConfigField:自定义常量,所有定义的常量都保存在BuildConfig.java类中,用于在Debug和Release下对常量定义不同的值
flavorDimensions:多个维度区分app版本,比如是否付费和渠道
单词解释
Build:构建
Variant:变体
Flavor:口味
示例
以友盟统计各渠道为例
meta标签
渠道信息在AndroidManifest.xml中定义
<meta-data
android:name="UMENG_CHANNEL"
android:value="${UMENG_CHANNEL_VALUE}" />
其中${UMENG_CHANNEL_VALUE}中的值需在build.gradle文件中通过manifestPlaceholders动态配置
productFlavors产品口味
build.gradle文件的productFlavors
productFlavors {
huawei {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "huawei"]
buildConfigField "String", "URL", "\"https://www.baidu.com\""
}
xiaomi {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "xiaomi"]
buildConfigField "String", "URL", "\"http://www.sina.com.cn\""
}
productFlavors.all { flavor ->
applicationVariants.all { variant ->
variant.outputs.each { output ->
def outputFile = output.outputFile
if (outputFile != null && outputFile.name.endsWith('.apk')) {
//定义生成包的名字格式:渠道名-版本号-构建类型,如:huawei-1.0.0-debug.apk
def fileName = "${variant.productFlavors[0].name}-${defaultConfig.versionName}-${variant.buildType.name}.apk"
output.outputFile = new File(outputFile.parent, fileName)
}
}
}
}
}
简洁写法
productFlavors {
huawei {
buildConfigField "String", "URL", "\"https://www.baidu.com\""
}
xiaomi {
buildConfigField "String", "URL", "\"http://www.sina.com.cn\""
}
productFlavors.all { flavor ->
flavor.manifestPlaceholders = [
UMENG_CHANNEL_VALUE: name
]
}
.....省略.....
}
UMENG_CHANNEL_VALUE对应的就是Androidmanifest文件配置${UMENG_CHANNEL_VALUE}的值
name对应的就是productFlavors的渠道名
buildConfigField对应的就是每个渠道的URL
manifestPlaceholders源码
public void setManifestPlaceholders(Map<String, Object> manifestPlaceholders) {
this.mManifestPlaceholders.clear();
this.mManifestPlaceholders.putAll(manifestPlaceholders);
}
接收的是一个Map,所以可以接收多个值,中间用逗号分隔
如:
huawei {
manifestPlaceholders = [
UMENG_CHANNEL_VALUE: "huawei",
UMENG_PUSH : "abcdef",
UMENG_UPDATE : "fedcba"]
buildConfigField "String", "URL", "\"https://www.baidu.com\""
}
buildConfigField源码
public void buildConfigField(String type, String name, String value) {
.....省略.....
}
接收三个参数,第一个参数为定义常量的类型,第二个参数为常量的命名,第三个参数为常量的值
如
buildConfigField "String", "URL", "\"https://www.baidu.com\""
buildConfigField "int", "AGE", "100"
这里定义的buildConfigField常量在BuildConfig.java类中都可以拿到
buildTypes构建类型
在buildTypes构建类型中配置buildConfigField、manifestPlaceholders值
buildTypes {
debug {
//指定签名为debug
signingConfig signingConfigs.debug
//log开关
debuggable true
//是否开启混淆
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
//自定义常量
buildConfigField "String", "URL", "\"https://www.baidu.com\""
//设置推送key
manifestPlaceholders = [PUSH_KEY: "debug_key"]
}
release {
//指定签名为release
signingConfig signingConfigs.release
//log开关
debuggable false
//是否开启混淆
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
//是否zip优化
zipAlignEnabled true
//删除无用资源
shrinkResources true
//自定义常量
buildConfigField "String", "URL", "\"http://www.sina.com.cn\""
//设置推送key
manifestPlaceholders = [PUSH_KEY: "release_key"]
}
}
buildTypes构建类型默认有debug、release两种,可以手动添加多种构建类型,在buildTypes中设置的buildConfigField、manifestPlaceholders值一般都是和构建类型有关,也无法在buildTypes中根据渠道来设置不同buildConfigField、manifestPlaceholders值。
如何根据渠道、构建类型来设置不同的值,还请往下看…
BuildVariants构建变体
buildTypes+productFlavors相结合,组成构建变体,buildTypes构建类型,主要就是debug和release的分别。productFlavors产品口味,主要就是各种渠道版本。两个合体就会构建出不同的版本apk (总apk个数=构建类型个数*渠道个数),如图
productFlavors多维度
当需要从多个维度区分app版本,比如是否付费和渠道时,就需要使用flavorDimensions来区分
flavorDimensions("isfree", "channel")
productFlavors {
free {
dimension "isfree"
}
paid {
dimension "isfree"
}
huawei {
manifestPlaceholders = [
UMENG_CHANNEL_VALUE : "huawei",
UMENG_PUSH : "abcdef",
UMENG_UPDATE: "fedcba"
]
buildConfigField "String", "URL", "\"https://www.baidu.com\""
buildConfigField "int", "AGE", "100"
dimension "channel"
}
xiaomi {
manifestPlaceholders = [
UMENG_CHANNEL_VALUE : "xiaomi",
UMENG_PUSH : "abcdef",
UMENG_UPDATE: "fedcba",
]
buildConfigField "String", "URL", "\"http://www.sina.com.cn\""
buildConfigField "int", "AGE", "200"
dimension "channel"
}
.....省略.....
}
注意
当添加了flavorDimensions,必须为每个productFlavors添加dimension,否则会提示错误
flavorDimensions源码
public void flavorDimensions(String... dimensions) {
this.checkWritability();
this.flavorDimensionList = Arrays.asList(dimensions);
}
flavorDimensions的形参是可变参数,说明可以接收多个参数,随后通过asList把传进来的参数转换成有顺序的列表。
此时在BuildVariants中会生成8种变体(总apk个数=构建类型个数*渠道个数*维度个数)
如图
flavorDimensions顺序很重要,当合并两个不同的Flavors时
res目录下的资源文件会遵循优先级覆盖的原则:
ProductFlavor资源文件会覆盖main的资源文件
flavorDimensions中定义维度是顺序的,决定了ProductFlavor之间资源覆盖的顺序,顺序在前的优先级越高,高优先级会覆盖低优先级的资源
注意
在gradle:3.0.0以上,在build.gradle里必须要有flavorDimensions字段,哪怕只有一个维度也要声明,否则报错
Error:All flavors must now belong to a named flavor dimension.Learn more at …
在gradle:3.0.0以上,当只有一个维度时也可以直接在defaultConfig中配置
defaultConfig {
.....省略.....
flavorDimensions "isfree"
}
BuildConfig类
public final class BuildConfig {
public static final boolean DEBUG = Boolean.parseBoolean("true");
public static final String APPLICATION_ID = "com.cn.liuyz.javademo";
public static final String BUILD_TYPE = "debug";
public static final String FLAVOR = "freeHuawei";
public static final int VERSION_CODE = 1;
public static final String VERSION_NAME = "1.0";
public static final String FLAVOR_isfree = "free";
public static final String FLAVOR_channel = "huawei";
// Fields from product flavor: huawei
public static final int AGE = 100;
public static final String URL = "https://www.baidu.com";
}
高级变体
假设小米、华为两个渠道对应的applicationId不同(相当于两个应用),每个渠道又对应两种构建类型debug和release,即一共有4种变体如:huaweiDebug,huaweiRelease,xiaomiDebug,xiaoRelease。现在要给每一种变体设置不同的第三方推送的appkey,那在build.gradle中如何动态配置呢?且看以下代码
huawei {
applicationId 'com.cn.liuyz.javademo1'
buildConfigField "int", "AGE", "1"
}
xiaomi {
applicationId 'com.cn.liuyz.javademo2'
buildConfigField "int", "AGE", "2"
}
productFlavors.all { flavor ->
applicationVariants.all { variant ->
//获取ProductFlavor
def mergedFlavor = variant.mergedFlavor
//动态给每一种变体设置不同的appkey
switch (variant.flavorName) {
case "huawei":
switch (variant.buildType.name) {
case "debug":
mergedFlavor.manifestPlaceholders = [
UMENG_CHANNEL_VALUE: "huawei",
UMENG_PUSH : "abcdef",
UMENG_UPDATE : "fedcba",
GETUIPP_ID : "huawei-debug",
DESIGN_WIDTH : "1",
DESIGN_HEIGHT : "2"
]
break
case "release":
mergedFlavor.manifestPlaceholders = [
UMENG_CHANNEL_VALUE: "huawei",
UMENG_PUSH : "abcdef",
UMENG_UPDATE : "fedcba",
GETUIPP_ID : "huawei-release",
DESIGN_WIDTH : "3",
DESIGN_HEIGHT : "4"
]
break
default:
break
}
break
case "xiaomi":
switch (variant.buildType.name) {
case "debug":
mergedFlavor.manifestPlaceholders = [
UMENG_CHANNEL_VALUE: "huawei",
UMENG_PUSH : "abcdef",
UMENG_UPDATE : "fedcba",
GETUIPP_ID : "xiaomi-debug",
DESIGN_WIDTH : "5",
DESIGN_HEIGHT : "6"
]
break
case "release":
mergedFlavor.manifestPlaceholders = [
UMENG_CHANNEL_VALUE: "huawei",
UMENG_PUSH : "abcdef",
UMENG_UPDATE : "fedcba",
GETUIPP_ID : "xiaomi-release",
DESIGN_WIDTH : "7",
DESIGN_HEIGHT : "8"
]
break
default:
break
}
break
}
//循环打包
.....省略.....
}
}
}
只要在第17行获取ProductFlavor,剩下的就和在每个渠道配置不同的key一样了。
动态版本号
应用不同,版本号肯定也不相同,如上高级变体中出现的huawei、xiaomi两个应用的版本号不可能总是相同的,那应该如何动态设置呢?且看下面代码
/**
* 设置获取版本号
* @param channel 渠道
* @return 返回版本号
*/
def getVersionCode(String channel) {
switch (channel) {
case "huawei":
111
break
case "xiaomi":
222
break
}
}
/**
* 设置获取版本名
* @param channel 渠道
* @return 返回版本名
*/
def getVersionName(String channel) {
switch (channel) {
case "huawei":
"1.1.1"
break
case "xiaomi":
"2.2.2"
break
}
}
android {
.....省略.....
productFlavors {
.....省略.....
productFlavors.all { flavor ->
applicationVariants.all { variant ->
//获取ProductFlavor
def mergedFlavor = variant.mergedFlavor
//动态给每一种变体设置不同的appkey
switch (variant.flavorName) {
case "huawei":
mergedFlavor.versionCode = getVersionCode("huawei")
mergedFlavor.versionName = getVersionName("huawei")
switch (variant.buildType.name) {
case "debug":
mergedFlavor.manifestPlaceholders = [
UMENG_CHANNEL_VALUE: "huawei",
UMENG_PUSH : "abcdef",
UMENG_UPDATE : "fedcba",
GETUIPP_ID : "huawei-debug",
DESIGN_WIDTH : "1",
DESIGN_HEIGHT : "2"
]
break
case "release":
mergedFlavor.manifestPlaceholders = [
UMENG_CHANNEL_VALUE: "huawei",
UMENG_PUSH : "abcdef",
UMENG_UPDATE : "fedcba",
GETUIPP_ID : "huawei-release",
DESIGN_WIDTH : "3",
DESIGN_HEIGHT : "4"
]
break
default:
break
}
break
case "xiaomi":
mergedFlavor.versionCode = getVersionCode("xiaomi")
mergedFlavor.versionName = getVersionName("xiaomi")
switch (variant.buildType.name) {
case "debug":
mergedFlavor.manifestPlaceholders = [
UMENG_CHANNEL_VALUE: "huawei",
UMENG_PUSH : "abcdef",
UMENG_UPDATE : "fedcba",
GETUIPP_ID : "xiaomi-debug",
DESIGN_WIDTH : "5",
DESIGN_HEIGHT : "6"
]
break
case "release":
mergedFlavor.manifestPlaceholders = [
UMENG_CHANNEL_VALUE: "huawei",
UMENG_PUSH : "abcdef",
UMENG_UPDATE : "fedcba",
GETUIPP_ID : "xiaomi-release",
DESIGN_WIDTH : "7",
DESIGN_HEIGHT : "8"
]
break
default:
break
}
break
}
//循环打包
.....省略.....
}
}
}
}
变体过滤器
有时某些BuildTypes+productFlavors结合没有意义,我们需要告诉Gradle不要生成这些Variants,这时需要设置variantFilter变体过滤器
android {
.....省略.....
android.variantFilter { variant ->
//是debug版本则忽略huaweiDebug渠道,其他debug包都打
if (variant.buildType.name.equals("debug")) {
variant.setIgnore(variant.getFlavors().get(0).name.equals("huawei"))
}
//是release版本则除了huaweiRelease渠道,其他都不能打包
if (variant.buildType.name.equals("release")) {
variant.setIgnore(!variant.getFlavors().get(0).name.equals("huawei"))
}
}
.....省略.....
}
这时只会生成huaweiRelease,xiaomiDebug两种apk版本
如图
单渠道依赖
单独只为某一个Variants变体添加一些依赖,只需要在Compile加上对应的Variant名字前缀就可以
dependencies {
huaweiCompile 'com.android.support:appcompat-v7:25.0.0'//只为hauwei渠道添加这个依赖
}
打包
一次生成所有渠道包
打开命令行窗口,进入到工程的根目录下,输入
gradlew assembleRelease或 gradlew assembleDebug
gradlew这个命令的gralde的版本无法控制,有时候会莫名其妙的下载老版本的gradle,不推荐使用
推荐使用gradle(需配置gradle环境变量)
打开命令行窗口,进入到工程的根目录下,输入
gradle assembleRelease或 gradle assembleDebug
生成单个渠道的包命令
gradle assembleHuaweiRelease
所有生成的apk在项目的build\outputs\apk下