一文了解 Android 多渠道打包

多渠道打包是什么

多渠道打包(Multi-channel Packaging)是指为同一个应用生成多个不同的安装包(通常是APK文件).如下图所示,每个安装包可以包含不同的代码和资源。

一般来说我们使用多渠道打包是为了以下几点:

  • 减少包体积:以推送服务为例,在国内不同的手机厂商有不同的 SDK,我们为不同的厂商打不同的渠道包,就可以让该渠道的apk只使用指定厂商的SDK,从而减少包的大小。
  • 数据统计:根据渠道区分来源,统计各渠道的下载量以及覆盖率
  • 厂商适配:适配不同厂商的系统API以及合规要求等

多渠道打包的相关概念

在 Android 中,多渠道打包需要了解 VariantflavorDimensionsproductFlavorsbuildTypes 等概念。下面分别介绍:

  • 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")
    }
}

参考

相关推荐
阿巴斯甜15 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker16 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952717 小时前
Andorid Google 登录接入文档
android
黄林晴18 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android