使用gradle多渠道打包也不是什么新鲜事了,配置productFlavors就完事了,再写点buildConfigField什么的,似乎也就可以用了。
用确实是可以用,但遇上某天想改打包出来的名字就很尴尬了,不知道怎么改。
使用
本来在build.gradle有这样的配置:
android {
productFlavors {
jinxin_bcca {
manifestPlaceholders = [CHANNEL_VALUE: "bcca"]
buildConfigField "String", "BCCA_HOST", '"222.182.202.98"'
buildConfigField "int","BCCA_PORT",'9529'
buildConfigField "int","DEVICE_TYPE",'0x00000631'
}
jinxin_bcca_debug {
manifestPlaceholders = [CHANNEL_VALUE: "bcca_debug"]
buildConfigField "String", "BCCA_HOST", '"39.105.93.18"'
buildConfigField "int","BCCA_PORT",'9529'
buildConfigField "int","DEVICE_TYPE",'0x00000601'
}
}
}
然后加一段配置代码:
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> {
/**
* Executes the given action against all objects in this collection, and any objects subsequently added to this
* collection.
*
* @param action The action to be executed
*/
void all(Action<? super T> action);
/**
* Executes the given closure against all objects in this collection, and any objects subsequently added to this collection. The object is passed to the closure as the closure
* delegate. Alternatively, it is also passed as a parameter.
*
* @param action The action to be executed
*/
void all(Closure action);
}
这是一个实打实的Java接口。
“Groovy编译器产生的字节码和Java编译器产生的字节码是完全一样的。这就等于说,Groovy能够完全使用各种Java API。” ----《Groovy入门经典》
有了这种认知,改配置代码其实就和改Java代码一样了。
常用到的一些方法在接口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.182.202.98"'
buildConfigField "int","BCCA_PORT",'9529'
buildConfigField "int","DEVICE_TYPE",'0x00000631'
}
jinxin_bcca_debug {
manifestPlaceholders = [CHANNEL_VALUE: "bcca_debug"]
buildConfigField "String", "BCCA_HOST", '"39.105.93.18"'
buildConfigField "int","BCCA_PORT",'9529'
buildConfigField "int","DEVICE_TYPE",'0x00000601'
setVersionName("0.888")
}
}
首先找到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.105.93.18"'
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:
productFlavors {
jinxin_bcca_debug {
manifestPlaceholders = [CHANNEL_VALUE: "bcca_debug"]
buildConfigField "String", "BCCA_HOST", '"39.105.93.18"'
buildConfigField "int","BCCA_PORT",'9529'
buildConfigField "int","DEVICE_TYPE",'0x00000601'
setVersionNameSuffix("192.168.0.1")
}
}
只要不与variant.getVersionName()同时使用即可,因为setVersionNameSuffix会让variant.getVersionName()返回VersionNameSuffix(有点坑)。那么最终打包可以这么使用了:
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
最好以后能想到更好的办法,不然会因为强迫症发作而疯掉。