Gradle的高级技巧

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下

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值