多渠道打包是什么
多渠道打包(Multi-channel Packaging)是指为同一个应用生成多个不同的安装包(通常是APK文件).如下图所示,每个安装包可以包含不同的代码和资源。
一般来说我们使用多渠道打包是为了以下几点:
- 减少包体积:以推送服务为例,在国内不同的手机厂商有不同的 SDK,我们为不同的厂商打不同的渠道包,就可以让该渠道的apk只使用指定厂商的SDK,从而减少包的大小。
- 数据统计:根据渠道区分来源,统计各渠道的下载量以及覆盖率
- 厂商适配:适配不同厂商的系统API以及合规要求等
多渠道打包的相关概念
在 Android 中,多渠道打包需要了解 Variant
、flavorDimensions
、productFlavors
和buildTypes
等概念。下面分别介绍:
Variant
中文翻译是变体 ,是指可以构建不同的版本的构建配置。productFlavors
+buildTypes
的组合结果就是一个个的变体。productFlavors
是指产品变体,比如 免费版、付费版。一般productFlavors
用于给特点变体配置不同的代码和资源;buildTypes
是构建类型,用来定义构建类型配置,比如是否开启代码混淆、是否开启调试模式等,通常包含debug和release两种类型。flavorDimensions
是指productFlavors
的维度。比如产品在是否付费维度 可以分成 免费和付费;在发布平台维度 可以分为 oppo 应用商店、华为应用商店等。flavorDimensions
的作用简单来说就是把productFlavors
分组。当只有 是否付费维度 时,变体有1 * productFlavors 数量 * buildTypes 数量
种配置;而当多了一个维度时,变体就有flavorDimensions数量 * productFlavors 数量 * buildTypes 数量
种配置。
为不同变体配置不同的代码和资源
多渠道打包的相关配置的代码如下所示:
javascript
// 注意,flavorDimensions 时必须声明的
flavorDimensions += listOf("platform")
productFlavors {
create("huawei") {
dimension = "platform"
}
create("oppo") {
dimension = "platform"
}
}
// 定义 buildTypes
buildTypes {
getByName("debug") {
isDebuggable = true
}
getByName("release") {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
如果我们需要为不同的 productFlavors
,即为 oppo 或者 huawei 的变体提供不同的代码和资源,可以直接在 src 目录下创建对应的目录,如下所示:
bash
src/
├── main/ # 公共代码和资源
├── oppo/ # oppo 风味的专属代码和资源
│ ├── java/
│ ├── res/
│ └── AndroidManifest.xml
└── huawei/ # huawei 风味的专属代码和资源
├── java/
├── res/
└── AndroidManifest.xml
可以看到 main 目录下的代码和资源是渠道包共有的,而 oppo、huawei 目录下的包是该渠道独有的。
资源合并规则
- 渠道构建时,渠道变体(huawei)会跟主变体(main)目录下的资源进行合并;
- 如有同名配置资源,例如strings.xml文件中的app_name,则优先取渠道(huawei)配置资源进行覆盖,其他不同名的则进行合并;
- layout文件、assets文件则是替换,渠道资源(huawei)优先于主变体(main)资源;
代码合并规则
代码文件是不支持合并的,也不支持同名。如果 huawei 目录和 main 目录下有一个相同的类,则会出现同名异常(Redeclaration)。
修改默认渠道路径
我们可以使用sourceSets
来为渠道指定代码路径,以及res、manifest等资源文件路径。代码示例如下所示,让oppo渠道复用huawei渠道的代码。
javascript
android {
productFlavors {
create("oppo") { }
}
// 需要注意的是,因为Gradle解析build.gradle脚本文件是自上而下的顺序,
// 所以sourceSets代码块要写在productFlavors代码块的后面,
// 否则会出现因为还没有解析渠道配置而找不到渠道的情况。
sourceSets {
getByName("oppo"){
java.srcDirs("src/oppo/java", "src/huawei/java")
kotlin.srcDirs("src/huawei/kotlin")
aidl.srcDirs("src/huawei/aidl")
res.srcDirs("src/huawei/res")
assets.srcDirs("src/huawei/assets")
jniLibs.srcDirs("src/huawei/jniLibs")
renderscript.srcDirs("src/huawei/rs")
manifest.srcFile("src/huawei/AndroidManifest.xml")
}
}
}
给不同的变体设置不同的配置
除了给不同的渠道包设置不同的资源和代码外,我们还可以为渠道包设置不同的配置。代码示例如下:
ini
productFlavors {
create("huawei") {
dimension = "platform"
applicationIdSuffix = ".huawei" // 在默认应用 ID 后添加 ".huawei" 后缀
versionNameSuffix = "-huawei" // 在版本名称后添加 "-huawei" 后缀
versionCode = 1 // 设置版本代码为 1
buildConfigField("int", "CHANNEL_CODE", "1001") // 在 BuildConfig 中生成一个名为 CHANNEL_CODE 的 int 类型字段,值为 1001
}
create("oppo") {
dimension = "platform"
applicationIdSuffix = ".oppo" // 在默认应用 ID 后添加 ".oppo" 后缀
versionNameSuffix = "-oppo" // 在版本名称后添加 "-oppo" 后缀
versionCode = 1 // 设置版本代码为 1
buildConfigField("int", "CHANNEL_CODE", "1002") // 在 BuildConfig 中生成一个名为 CHANNEL_CODE 的 int 类型字段,值为 1002
}
}
给不同的渠道设置不同的依赖项
代码示例如下,我们可以为不同的渠道设置不同的依赖。
arduino
"huaweiImplementation"("com.huawei.hms:push:7.1.0.100")
多渠道依赖方式如下所示:
- 默认依赖:implementation
- 渠道依赖:变体+Implementation,如huaweiImplementation
- 构建类型:类型+Implementation,如debugImplementation
- 组合变体:变体+类型+Implementation,如huaweiDebugImplementation
打包
如果我们想要打出所有的渠道包,可以执行 assemble 任务,结果如下图所示:
如果只想打一个渠道包,则可以执行对应的 assembleXXX 任务,如下所示:
某些情况下,想在渠道构建中去掉某个渠道,这时候我们可以创建变体过滤器
来移除指定渠道。代码示例如下:
bash
androidComponents {
beforeVariants { variantBuilder ->
if (variantBuilder.productFlavors.containsAll(listOf("platform" to "huawei"))) {
variantBuilder.enable = false
}
}
}
统计渠道
我们可以通过在打包前预置渠道信息,然后在运行时获取并上报,从而实现渠道的数据统计的效果。方式如下:
- 在不同渠道的 AndroidManifest.xml文件中使用meta-data标签
代码示例如下:
javascript
<application ...>
<meta-data android:name="UMENG_CHANNEL" android:value="${UMENG_CHANNEL_NAME}"/>
...
</application>
productFlavors {
create("huawei") {
manifestPlaceholders["UMENG_CHANNEL_NAME"] = "huawei"
}
create("oppo") {
manifestPlaceholders["UMENG_CHANNEL_NAME"] = "oppo"
}
}
- 往 BuildConfig 设置渠道信息
代码示例如下:
javascript
productFlavors {
create("huawei") {
buildConfigField("int", "CHANNEL_CODE", "1001")
}
create("oppo") {
buildConfigField("int", "CHANNEL_CODE", "1002")
}
}