什么是多渠道包
渠道包就是要在安装包apk中添加渠道信息,也就是channel,对应不同的渠道,例如:小米市场、360市场、应用宝市场等。需要为每个apk包设定一个可以区分渠道的标识,这个为apk包设定应用市场标识的过程就是多渠道打包。
为什么要提供多渠道包
国内存在着有众多的应用市场,产品在不同的渠道可能有不同的统计需求,为此Android开发人员需要为每个应用市场发布一个安装包,在安装包中添加不同的标识,应用在请求网络的时候携带渠道信息,方便后台做运营统计。
通过配置gradle实现多渠道打包的方式
其核心原理就是在编译时通过gradle修改AndroidManifest.xml中的meta-data占位符的内容,执行N次打包流程,然后就可以在java中通过API获取对应的数据。
BuildTypes、Flavors、BuildVariants
1. BuildTypes : 构建类型,gradle组件默认提供给了debug,release构件类型。
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.Release
zipAlignEnabled true
setDebuggable false
}
debug {
minifyEnabled false
setDebuggable true
testCoverageEnabled true
}
}
2. Flavors : 产品渠道,可以根据productFlavors,针对不同的渠道生产个性化apk。
productFlavors {
xiaomi {
dimension EDITION
buildConfigField "String", "PARTNER_CODE", "XM""
}
huawei {
dimension EDITION
buildConfigField "String", "PARTNER_CODE", "HW""
//添加Config配置参数,项目中可以通过Config.PARTNER_CODE 获取
}
}
3. BuildVariants:每一个buildtype和flavor组成一个buildvariant。
在Android对 Gradle 插件的扩展支持中,其中最常见的便是利用 variant 来对构建过程中各默认 task 进行 hook,关于 variants的类型,一共可分为3种类型
applicationVariants:只适用 app plugin
Manifest配置渠道占位符
<meta-data
android:name="channel"
android:value="${channel_value}"/> //yyb,360
app模块的build.gradle
android{
//产品维度,不同维度用","分隔。下面维度为产品+型号组合
flavorDimensions 'product', 'model'
//productFlavors是android节点下的一个节点
productFlavors {
baidu {
dimension 'product'
}
xiaomi {
dimension 'product'
}
yyb {
dimension 'product'
}
model1 {
dimension 'model'
}
model2 {
dimension 'model'
}
}
}
//如果需要在不同渠道统一配置,使用productFlavors.all字段
//替换manifest里面channel_value的值,
//遍历productFlavors,他们的 channel_value 设置为自己的 name(baidu。xiaomi。。。)
productFlavors.all { flavor ->
manifestPlaceholders = [channel_value: name]}
//设置渠道包名称-> howow-xiaomi-release-1.0.apk
applicationVariants.all { variant ->
variant.outputs.all { output ->
//output即为该渠道最终的打包产物对象
//设置它的outputFileName属性
outputFileName = "hwow_${variant.productFlavors[0].name}_${variant.buildType.name}_${variant.versionName}.apk"
}
}
}
运行时获取渠道信息
private String getChannel() {
PackageManager pm = getPackageManager();
ApplicationInfo appInfo = pm.getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA);
return appInfo.metaData.getString("channel_value");
}
libraryVariants:只适用 library plugin library构建变种。例如包名
libraryVariants.all { variant ->
def editionName = ""
def configName = ""
def flavor
variant.outputs.all { output ->
productFlavors.each { productFlavor ->
flavor = productFlavor
switch (productFlavor.dimension) {
case CONFIG:
configName = productFlavor.name
break
case EDITION:
editionName = productFlavor.name
break
}
}
//添加项目/仓库名称
def fileNamePrefix = "${PROJECT_NAME}"
//添加渠道/省份名称,垂直市场建议使用省份拼音简称命名
switch (editionName) {
case "market":
fileNamePrefix += "-all"
break
default:
fileNamePrefix += "-${editionName}"
break
}
//添加partnerCode
def partnerCode = "all"
def partnerCodeField = flavor.getBuildConfigFields().get("PARTNER_CODE")
if (partnerCodeField != null) {
partnerCode = partnerCodeField.getValue()
partnerCode = partnerCode.replace("\"", "")
}
fileNamePrefix += "-${partnerCode}"
if (editionName == "xiaomi" ) {
fileNamePrefix += "-multi"
}
//添加环境标识,online,uat等
fileNamePrefix += "-online"
//添加版本号
fileNamePrefix += "-${android.defaultConfig.versionName}"
//添加时间戳
fileNamePrefix += "-${releaseTime()}"
//添加功能标识,混淆
if (buildType.minifyEnabled) {
fileNamePrefix += "-obfuscated"
}
//添加签名标识
if (buildType.name == "debug") {
fileNamePrefix += "-debug"
} else {
fileNamePrefix += "-release"
}
println fileNamePrefix
outputFileName = "${fileNamePrefix}.aar"
}
}
testVariants:以上两者都适用
配置差异化的 logo 和 app名字,则可以在 gradle 中使用下面这段
android {
flavorDimensions('abi', 'version')
productFlavors {
// 省略其他的风味配置
x86 {
dimension 'abi'
// 配置不同的包名,达到能两个风味能共存
applicationId 'com.zinc.bear'
manifestPlaceholders = [
logo : "@drawable/logo_bear",
appName : "bear",
]
// 配置签名
signingConfig signingConfigs.jiangpengyong
}
armV7 {
dimension 'abi'
// 配置不同的包名,达到能两个风味能共存
applicationId 'com.zinc.shark'
manifestPlaceholders = [
logo : "@drawable/logo_shark",
appName : "shark",
]
// 配置签名
signingConfig signingConfigs.xiaopenyou
}
}
}
然后在 AndroidManifest.xml 中使用,使用 ${你配置的变量名}
<application
android:allowBackup="true"
android:icon="${logo}"
android:label="${appName}"
android:roundIcon="${logo}"
android:supportsRtl="true"
android:theme="@style/AppTheme">
......
但是,这种方式存在一些缺点:
1.每生成一个渠道包,都要重新执行一遍构建流程,效率太低,只适用于渠道较少的场景。
2.Gradle会为每个渠道包生成一个不同的BuildConfig.java类,记录渠道信息,导致每个渠道包的dex的CRC值都不同
public final class BuildConfig {
public static final boolean DEBUG = Boolean.parseBoolean("true");
public static final String APPLICATION_ID = "org.devio.as.proj.biz_home";
public static final String BUILD_TYPE = "debug";
public static final int VERSION_CODE = 1;
public static final String VERSION_NAME = "1.0";
//增加了productflavor之后,这里的值,每个apk都不同
public static final String FLAVOR = "";
}
如果使用了微信的Tinker热补丁方案,那么就需要为不同的渠道包打不同的补丁,因为Tinker是通过对比基础包base.apk和新包new.apk生成差分补丁patch.apk,然后再把补丁patch.apk和已安装的基础包old_base.apk一起合成新的apk。
这就要求用于生成差分补丁的基础包base.apk里面的DEX和已安装的基础包old_base.apk里面的DEX是完全一致的,就是说手机上安装的是应用宝渠道包,那必须使用应用宝渠道包来生成补丁patch.apk,才能合并成功)