目录
前言
前段时间在研究Android自定义插件的一系列内容(具体见
Android 自定义Gradle插件的多层属性扩展(五)
)的时候
,接触到android{}中的productFlavors该扩展属性,觉得很有意思,简单的总结下。
一 productFlavors
productFlavors是android{}中的一个扩展属性,主要用来让同一套代码可以打包成不同的apk包,又可以成为变体(Variants)。其实该productFlavors属性是与buildTypes配合使用的,后面会列举。先从一个实例中来看下这个扩展属性怎么用。
1.productFlavors基本使用
通常使用productFlavors{}可以使用一套代码来产生多个apk包,例如多渠道打包。
- (1)在AndroidManifest中添加一个<meta-data>
添加该<meta-data>用来标记多渠道,代码如下:
<!--设置多渠道-->
<meta-data
android:name="channel"
android:value="${CHANNEL}" />
- (2)在build.gradle中添加该productFlavors{}
在app下的build.gradle中通过productFlavors为AndroidManifest中的meta-data赋值,代码如下:
//特色产品:同一套代码生产出不同特色的产品
flavorDimensions "channel"
productFlavors {
huawei {
manifestPlaceholders = [CHANNEL: "huawei"]
dimension = "channel"
}
oppo {
manifestPlaceholders = [CHANNEL: "oppo"]
dimension = "channel"
}
xiaomi {
manifestPlaceholders = [CHANNEL: "xiaomi"]
dimension = "channel"
}
}
}
对于在productFlavors{}下面的每个闭包都是一个flavor,注意从Android Studio 3.0.0之后,在使用productFlavors{}的时候,必须为每个flavor设置一个dimension。这个dimension可以理解为对flavor进行分组,不同的dimension中的flavor会组合成一个变体variants。如果不设置dimension,则会在build输出窗口抛出下面异常:
All flavors must now belong to a named flavor dimension. Learn more at https://d.android.com/r/tools/flavorDimensions-missing-error-message.html
进入到错误的提示链接中发现:
第一句话明确说明需要给每一个flavor设置一个dimension。
当然官网也给到解决方案:使用flavorDimensions定义dimension,即apk的维度;然后给到每个闭包flavor设置对应的dimension即可,如同所示:
写在第一个的维度的优先级最高,最后会以所有维度的笛卡尔积来产生variant的数量,最后配合为每个variant添加buildTypes{}中的定义的类型,生成最后的variant。 对项目进行编译之后,可以看到在Build Variants窗口中就可以看到有6个variant。
默认的在通过Android Studio的Run运行,只会产生一个apk,但是通过Generate Signed Bundle or APK,即如下窗口
可以任意选择要生成签名的variant来进行打包签名。
最后在代码中可以打印出该<meta-data>中的值,代码如下:
private String getChannelFromAndroidManifest() {
String metaChannel = "";
try {
ApplicationInfo info = getPackageManager().getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA);
if (info == null || info.metaData == null) {
return metaChannel;
}
metaChannel = info.metaData.getString("channel");
} catch (PackageManager.NameNotFoundException e) {
}
return metaChannel;
}
发现与在build.gradle中设置的值保持一致。具体的代码已经上传到至
https://github.com/wenjing-bonnie/AndroidPlugin.git
的app目录的相关内容。因为逐渐在这基础上进行迭代,可回退到Gradle_5.0.1该tag下可以查看相关内容。
2. productFlavors的variants输出
其实在Android Gradle提供了对应的api来将输出所有的variant变体的信息,在项目配置完成之后,可以通过this.android.applicationVariants.all将所有的variants的信息输出,例如name:
this.afterEvaluate {
this.android.applicationVariants.all { variant ->
println(prefix + "name = " + variant.name)
}
}
通过Android Studio编译项目,发现Build输出窗口会输出每个variant的name,如下:
@@@@@@@@@@@@@@ app @@@@@@@@@@@@@ name = huaweiDebug
@@@@@@@@@@@@@@ app @@@@@@@@@@@@@ name = oppoDebug
@@@@@@@@@@@@@@ app @@@@@@@@@@@@@ name = xiaomiDebug
@@@@@@@@@@@@@@ app @@@@@@@@@@@@@ name = huaweiRelease
@@@@@@@@@@@@@@ app @@@@@@@@@@@@@ name = oppoRelease
@@@@@@@@@@@@@@ app @@@@@@@@@@@@@ name = xiaomiRelease
二 productFlavors应用场景
前面主要总结了productFlavors的一个基本使用,那么在Android开发中productFlavors可以用来做什么呢?
- 1.同一套代码维护了面向不同用户的apk
像一个APK可以打包成一个debug版本和一个release版本,需要在一个手机上同时安装这两个apk。我们知道对于这两个apk如果包名一致的话,肯定会相互覆盖,那么就可以采用productFlavors{}来为每个variant设置不同的applicationId,那么打包成的两个apk会共用同一套代码,但是包名不一致,所以可以同时安装在同一个手机上。相比较于
1.productFlavors基本使用
仅仅是将在不同的flavor闭包为不同的flavor设置”applicationId = xxxx”即可。
- 2.多渠道打包
像在
1.productFlavors基本使用
的例子就是设置的多渠道打包的方式,可以通过productFlavors{}对<meta-data> 设置不同的渠道值。
- 3.针对不同的手机设置不同的应用icon、文字或其他信息
像Android各大手机厂商,有自己的手机系统的应用icon的样式,那么就可以通过 productFlavors{}来设置不同的icon,其步骤大体如下:
(1)按照在
1.productFlavors基本使用
设置好不同的渠道标识;
(2)在项目的src的同级目录右击src目录,选择New Resource Directory ,在弹出的对话框中选择src/huawei/res依次新建放置icon的不同的资源包,如图所示:
创建之后的项目目录如下所示:
这样每个flavor会先读取本flavor对应的目录下读取对应的代码或者资源文件,如果没有的话,才会去main目录下进行查找。这样在Build Variants的窗口中选择huaweiDebug,运行到手机中,发现对应的icon已经更换
具体的代码已经上传到至
GitHub – wenjing-bonnie/AndroidPlugin: 用来学习Android Gradle Plugin
的app目录的相关内容。因为逐渐在这基础上进行迭代,可回退到Gradle_5.0.1该tag下可以查看相关内容。
对于其他内容如代码、文案等的修改同样如此。
- 4.简单的不同flavor值的设置
像在
1.productFlavors基本使用
使用的通过<meta-data>设置一个简单的字符串,在代码中取需要比较多的代码,才可以获取到该值,其实如果仅仅是简单的设置一些值的区别,可以直接使用buildConfigField来设置即可,代码如下:
productFlavors {
huawei {
manifestPlaceholders = [CHANNEL: "huawei"]
dimension = "channel"
buildConfigField("String","CHANNEL","\"huawei\"")
}
oppo {
manifestPlaceholders = [CHANNEL: "oppo"]
dimension = "channel"
buildConfigField("String","CHANNEL","\"oppo\"")
}
xiaomi {
manifestPlaceholders = [CHANNEL: "xiaomi"]
dimension = "channel"
buildConfigField("String","CHANNEL","\"xiaomi\"")
}
}
而在代码中只需要通过BuildConfig.CHANNEL就可以取得设置的渠道值。
- 5.对variant进行不同的处理
在前面的
2. productFlavors的variants输出
可以通过this.android.applicationVariants.all将所有的variants输出,那么就可以针对不同的variants来进行单独处理。例如可以得到每个variant的Task,针对所需的Task进行Hook,如图所示:
代码如下:
this.afterEvaluate {
//当前variant的类型为application类型
this.android.applicationVariants.all { ApplicationVariant variant ->
println(prefix + "name = " + variant.name)
//得到每个variant中的task,然后进行对Task进行hook
variant.getAssembleProvider().get().doFirst {
println(prefix + " assemble task do first ")
}
}
}
三 总结
productFlavors{}主要用来针对一套代码打包成多个有差异的apk。
- 1.productFlavors{}下的每个闭包就是一个flavor,也可称为变体variant;
- 2.定义一个variant,需要设定dimension。需要通过flavorDimensions来定义dimension;
- 3.每个dimension称为一个分组,多个不同的dimension通过笛卡尔积的形式得到一个variant,最后结合buildTypes{}里面配置的项,形成最终的variant
- 4.如果只是简单的逻辑的差异,可以通过BuildConfigField进行设置差异,当然可以通过<meta-data>的形式来设置差异;
- 5.对于每个variant在main的同级目录下都有一个对应的资源目录和代码目录,在打包成apk的时候,首先会读取对应的variant的相应目录,没有的话再去读取main下的目录内容;
- 6.可通过this.android.applicationVariants.all在项目配置完成之后,得到所有的variants,每个variant是一个ApplicationVariant,可以得到该variant中的task、name等信息
加油,越来越好玩了。