一文了解 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")
    }
}

参考

相关推荐
*星星之火*3 小时前
【GPT入门】第5课 思维链的提出与案例
android·gpt
EasyCVR4 小时前
EasyRTC嵌入式视频通话SDK的跨平台适配,构建web浏览器、Linux、ARM、安卓等终端的低延迟音视频通信
android·arm开发·网络协议·tcp/ip·音视频·webrtc
韩家老大4 小时前
RK Android14 在计算器内输入特定字符跳转到其他应用
android
张拭心7 小时前
2024 总结,我的停滞与觉醒
android·前端
夜晚中的人海7 小时前
【C语言】------ 实现扫雷游戏
android·c语言·游戏
ljx14000525508 小时前
Android AudioFlinger(一)——初识AndroidAudio Flinger
android
ljx14000525508 小时前
Android AudioFlinger(四)—— 揭开PlaybackThread面纱
android
Codingwiz_Joy8 小时前
Day04 模拟原生开发app过程 Androidstudio+逍遥模拟器
android·安全·web安全·安全性测试
叶羽西8 小时前
Android15 Camera框架中的StatusTracker
android·camera框架
梦中千秋8 小时前
安卓设备root检测与隐藏手段
android