使用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這個類,其繼承關係如圖:
在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"
}
}