关于gradle多渠道打包的命名

使用gradle多渠道打包也不是什么新鲜事了,配置productFlavors就完事了,再写点buildConfigField什么的,似乎也就可以用了。

用确实是可以用,但遇上某天想改打包出来的名字就很尴尬了,不知道怎么改。

使用

本来在build.gradle有这样的配置:

android {

    buildTypes {
        debug {
			//省略
        }

        release {
			//省略
        }
    }
    
    productFlavors {
        jinxin_bcca {
            manifestPlaceholders = [CHANNEL_VALUE: "bcca"]
            buildConfigField "String", "BCCA_HOST", '"222.82.20.98"'
            buildConfigField "int","BCCA_PORT",'9529'
            buildConfigField "int","DEVICE_TYPE",'0x00000631'
        }
        jinxin_bcca_debug {
            manifestPlaceholders = [CHANNEL_VALUE: "bcca_debug"]
            buildConfigField "String", "BCCA_HOST", '"39.10.93.118"'
            buildConfigField "int","BCCA_PORT",'9529'
            buildConfigField "int","DEVICE_TYPE",'0x00000601'
        }
    }
}

然后加一段配置apk文件名的代码:

            applicationVariants.all { variant ->
                variant.outputs.all { output ->
                    if (variant.buildType.name.equals('release')) {
                        outputFileName = "BeoneGateway3_V${defaultConfig.versionName}_${variant.productFlavors[0].getName()}.apk"
                    } else if (variant.buildType.name.equals('debug')) {
                        outputFileName = "BeoneGateway3_V${defaultConfig.versionName}_debug.apk"
                    }
                }
            }

这样使用了很长一段时间,这样打包出来的apk文件会被这段代码重新命名。一直以来,虽然不知道这段代码的具体语法,但是大概能揣摩每个变量的意思…也仅限于此。

摸索

后面稍微了解一下groovy语言,发现和java相当类似,那么又重新从语言的角度来看之前这段配置代码,于是改成了这样:

    applicationVariants.all { variant ->
        variant.outputs.all { output ->
            if (variant.buildType.name.equals('release')) {
                outputFileName = "SmartStreamGateway_V${defaultConfig.versionName}_${variant.getFlavorName()}.apk"
            } else if (variant.buildType.name.equals('debug')) {
                outputFileName = "SmartStreamGateway_V${defaultConfig.versionName}_${variant.getFlavorName()}_debug.apk"
            }
        }
    }

并且也知道其中的variant其实就当于fori循环中的i一样,相当于一个形参,而{…}这一段就是一个闭包。更重要的是,gradle本身就是一个框架,其中也包含着各种类各种方法,这里的applicationVariants.all来自于定义:

public interface DomainObjectCollection<T> extends Collection<T> {

    void all(Action<? super T> action);

    void all(Closure action);
}

这里出现的variant:

public interface ApplicationVariant extends ApkVariant, TestedVariant {}

这些都是实打实的Java接口。

“Groovy编译器产生的字节码和Java编译器产生的字节码是完全一样的。这就等于说,Groovy能够完全使用各种Java API。” ----《Groovy入门经典》

有了这种认知,改配置代码其实就和改Java代码一样了。

ApplicationVariant常用到的一些方法都定义在父类*N的接口BaseVariant中:

public interface BaseVariant {

    @NonNull
    String getName();

    @NonNull
    String getDescription();

    @NonNull
    String getDirName();

    @NonNull
    String getFlavorName();

    @NonNull
    BuildType getBuildType();

    @NonNull
    ProductFlavor getMergedFlavor();

    @NonNull
    List<ProductFlavor> getProductFlavors();

    @NonNull
    List<SourceProvider> getSourceSets();

    @NonNull
    String getApplicationId();

    void buildConfigField(@NonNull String type, @NonNull String name, @NonNull String value);

    void resValue(@NonNull String type, @NonNull String name, @NonNull String value);
}

那么问题来了,现在我想以自己定义的一些值作为文件名,而且不能体现在ProductFlavor的名称中,因为这些值类似于"192.168.0.1",而这种命名不能用在这里(因为分隔符‘.'的原因)。

在上面的方法中有个叫getDescription的函数,本来以为在实现类中应该会有相应的setDescription方法,但事实证明想多了…

通过源码一路查找BaseVariantImpl–>BaseVariantData–>AndroidArtifactVariantData–>InstallableVariantData–>ApkVariantData:

public abstract class ApkVariantData extends InstallableVariantData {
    @Override
    @NonNull
    public String getDescription() {
        final GradleVariantConfiguration config = getVariantConfiguration();

        if (config.hasFlavors()) {
            StringBuilder sb = new StringBuilder(50);
            StringHelper.appendCapitalized(sb, config.getBuildType().getName());
            StringHelper.appendCapitalized(sb, config.getFlavorName());
            sb.append(" build");
            return sb.toString();
        } else {
            return StringHelper.capitalizeWithSuffix(config.getBuildType().getName(), " build");
        }
    }
}

发现这个方法是返回的“固定值”,类似于“buildType FlavorName build"这样的形式。于是死心。

还是得找找ProductFlavor类里有没有可以用来作为命名变量的东西,但与上面的ApkVariantData类似,许多值是返回的既定值,并不能自行定义,除了…类似buildConfigField这样的…

发现

回头看最开始使用多渠道时,使用productFlavors时的场景,其中使用buildConfigField来定义一些运行时的常量,以便在各个渠道时各自对应。

    productFlavors {
        jinxin_bcca {
            manifestPlaceholders = [CHANNEL_VALUE: "bcca"]
            buildConfigField "String", "BCCA_HOST", '"222.82.20.98"'
            buildConfigField "int","BCCA_PORT",'9529'
            buildConfigField "int","DEVICE_TYPE",'0x00000631'
        }
        jinxin_bcca_debug {
            manifestPlaceholders = [CHANNEL_VALUE: "bcca_debug"]
            buildConfigField "String", "BCCA_HOST", '"39.10.93.118"'
            buildConfigField "int","BCCA_PORT",'9529'
            buildConfigField "int","DEVICE_TYPE",'0x00000601'
            setVersionName("0.888")
        }
    }

首先找到ProductFlavor这个类,其继承关系如图:
productFlavor
在BaseConfigImpl中是明确可以拿到由buildConfigField定义的值:

public abstract class BaseConfigImpl implements Serializable, BaseConfig {
    private final Map<String, ClassField> mBuildConfigFields = Maps.newTreeMap();
    
    @Override
    @NonNull
    public Map<String, ClassField> getBuildConfigFields() {
        return mBuildConfigFields;
    }
}

而buildConfigField其实也是一个方法,在BaseFlavor中:

public abstract class BaseFlavor extends DefaultProductFlavor implements CoreProductFlavor {

    public void buildConfigField(
            @NonNull String type, @NonNull String name, @NonNull String value) {
        ClassField alreadyPresent = getBuildConfigFields().get(name);
        if (alreadyPresent != null) {
            String flavorName = getName();
            if (BuilderConstants.MAIN.equals(flavorName)) {
                logger.info(
                        "DefaultConfig: buildConfigField '{}' value is being replaced: {} -> {}",
                        name,
                        alreadyPresent.getValue(),
                        value);
            } else {
                logger.info(
                        "ProductFlavor({}): buildConfigField '{}' "
                                + "value is being replaced: {} -> {}",
                        flavorName,
                        name,
                        alreadyPresent.getValue(),
                        value);
            }
        }
        addBuildConfigField(new ClassFieldImpl(type, name, value));
    }
}

那么理论上所有在这一系列定义的public方法,其实都可以在build.gradle中的productFlavors中实用,比如这样:

    productFlavors {
        jinxin_bcca_debug {
            manifestPlaceholders = [CHANNEL_VALUE: "bcca_debug"]
            buildConfigField "String", "BCCA_HOST", '"39.10.93.118"'
            buildConfigField "int","BCCA_PORT",'9529'
            buildConfigField "int","DEVICE_TYPE",'0x00000601'
            setVersionName("0.888")
        }
    }

因为DefaultProductFlavor中定义了此方法:

//DefaultProductFlavor.java
    @NonNull
    public ProductFlavor setVersionName(String versionName) {
        mVersionName = versionName;
        return this;
    }

最终可以使用下面的函数拿到设定的值也就是0.888:

${variant.getProductFlavors()[0].getVersionName()}

其他方法也是类似。

对BuildConfigField而言,理论上是可以这样操作的:

            buildConfigField "String", "CUSTOM", '"测试用"'//定义
            
            variant.getProductFlavors()[0].getBuildConfigFields().get("CUSTOM").getValue()//取值

但实际操作中在某种情况下会出现异常,编译器会认为get到的东西为null(哪怕实际编译后并不为null):

Cannot invoke method getValue() on null object
结果

所以经过权衡…目前可以使用VersionNameSuffix,这个方法在BaseConfigImpl中实现,层级足够高,可以被任何ProductFlavor获得:

    productFlavors {
        jinxin_bcca_debug {
            manifestPlaceholders = [CHANNEL_VALUE: "bcca_debug"]
            buildConfigField "String", "BCCA_HOST", '"39.10.93.118"'
            buildConfigField "int","BCCA_PORT",'9529'
            buildConfigField "int","DEVICE_TYPE",'0x00000601'
            setVersionNameSuffix("192.168.0.1")
        }
    }

理论上这个方法只作为versoinName的后缀存在,然后在使用getVersionName()时可以自动获取带后缀的版本名,但现在得避免getVersionName()出现了:

    applicationVariants.all { variant ->
        variant.outputs.all { output ->
            if (variant.buildType.name.equals('release')) {
                outputFileName = "SmartStreamGateway_V${defaultConfig.versionName}_${variant.getFlavorName()}_${variant.getProductFlavors()[0].getVersionNameSuffix()}.apk"
            } else if (variant.buildType.name.equals('debug')) {
                outputFileName = "SmartStreamGateway_V${defaultConfig.versionName}_${variant.getFlavorName()}_${variant.getProductFlavors()[0].getVersionNameSuffix()}_debug.apk"
            }
        }
    }

最终输出的文件名(在debug状态下)是:

SmartStreamGateway_V0.60_jinxin_bcca_debug_192.168.0.1_debug.apk

也可以使用mergedFlavor可以用来应对。

    applicationVariants.all { variant ->
        variant.outputs.all { output ->
            if (variant.buildType.name.equals('release')) {
                outputFileName = "SmartStreamGateway_V${defaultConfig.versionName}_${variant.getFlavorName()}_${variant.getMergedFlavor().getVersionNameSuffix()}.apk"
            } else if (variant.buildType.name.equals('debug')) {
                outputFileName = "SmartStreamGateway_V${defaultConfig.versionName}_${variant.getFlavorName()}_${variant.getMergedFlavor().getVersionNameSuffix()}_debug.apk"
            }
        }
    }

getMergedFalvor会得到这么一个Flavor对象,这个对象是由build.gradle中配置的defaultConfig创建而得的。而这个ProductFlavor同样可以使用getVersionNameSuffix函数得到想要的值。

或者不手动判定buildType和ApplicationId,直接加到文件名中也是可以的:

    applicationVariants.all { variant ->
        variant.outputs.all { output ->
            outputFileName = "SmartStreamGateway_V${defaultConfig.versionName}_${variant.getFlavorName()}_${variant.getMergedFlavor().getVersionNameSuffix()}_${variant.buildType.name}.apk"
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值