75、多渠道打包:productFlavors、友盟多渠道、美团Walle、V2签名

多渠道打包,说白了就是同一份代码,打出不同渠道的APK。

你想想看,应用商店有华为、小米、应用宝、360……每个渠道都要统计下载量、激活量。总不能每个渠道单独维护一个工程吧?

我刚开始做Android时,就干过这种蠢事——手动改包名、改渠道号,然后一个一个点“Build APK”。那滋味,谁试谁知道。

75.1 为什么需要多渠道打包?

核心需求其实就两个:

  • 统计区分:知道用户是从哪个渠道来的
  • 差异化配置:不同渠道可能有不同的SDK、不同的启动页

嗯,这里要注意:多渠道 ≠ 多包名。包名通常只有一个,渠道只是APK里的一个标记位。

75.2 方案一:productFlavors(原生方案)

这是Gradle自带的方案,最正统,但也是最慢的。

核心思路:在build.gradle里定义flavor维度,每个flavor可以覆盖资源、代码、依赖。

android {
    flavorDimensions "channel"
    productFlavors {
        huawei {
            dimension "channel"
            manifestPlaceholders = [CHANNEL_VALUE: "huawei"]
        }
        xiaomi {
            dimension "channel"
            manifestPlaceholders = [CHANNEL_VALUE: "xiaomi"]
        }
        yingyongbao {
            dimension "channel"
            manifestPlaceholders = [CHANNEL_VALUE: "yingyongbao"]
        }
    }
}

然后在AndroidManifest.xml里引用占位符:

<meta-data
    android:name="CHANNEL"
    android:value="${CHANNEL_VALUE}" />

运行时读取:

public static String getChannel(Context context) {
    try {
        ApplicationInfo ai = context.getPackageManager()
                .getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
        return ai.metaData.getString("CHANNEL");
    } catch (Exception e) {
        return "unknown";
    }
}

我曾经踩过的坑:productFlavors每次打包都会走完整的编译流程。如果你有30个渠道,就要编译30次。我有个项目渠道数到了50个,一次打包要跑一个多小时。那段时间我每天下班前点一下打包,第二天早上来看结果。

75.3 方案二:友盟多渠道(UMeng方案)

友盟的方案其实是对productFlavors的简化封装。它利用Gradle的productFlavors,但把渠道号写在了AndroidManifest的meta-data里。

配置方式类似:

productFlavors {
    huawei {}
    xiaomi {}
    yingyongbao {}
}

productFlavors.all { flavor ->
    flavor.manifestPlaceholders = [CHANNEL_VALUE: name]
}

友盟SDK会自动读取这个meta-data,然后上报到友盟后台。

我的建议:如果你只用友盟统计,这个方案够用。但如果你还要接其他SDK(比如广告、推送),每个渠道的配置会变得很复杂。我个人更推荐用Walle。

75.4 方案三:美团Walle(推荐方案)

Walle是美团开源的多渠道打包工具。它的核心思想是:不重新编译,只修改APK的注释块

为什么快?因为编译一次,生成一个基础APK,然后往APK里写渠道信息。写一个渠道只需要几十毫秒。

原理:APK文件其实是一个ZIP包。ZIP文件末尾有一个Comment区域(最大65535字节)。Walle把渠道信息写在这个Comment里,不破坏签名。

接入方式:

// 项目根build.gradle
buildscript {
    dependencies {
        classpath 'com.meituan.android.walle:plugin:1.1.7'
    }
}

// app/build.gradle
apply plugin: 'walle'

walle {
    apkOutputPath = "${project.buildDir}/outputs/channels"
    channelFile = file("channel.txt")
}

channel.txt内容:

huawei
xiaomi
yingyongbao
360
oppo
vivo

打包命令:

./gradlew clean assembleReleaseChannels

读取渠道:

String channel = WalleChannelReader.getChannel(context);

我个人的经验:Walle是我目前最常用的方案。50个渠道,编译一次+写入50次,总共不到5分钟。相比productFlavors的一个多小时,简直是天壤之别。

75.5 V2签名与Walle的兼容性

Android 7.0(API 24)引入了V2签名方案。V2签名把签名信息放在了APK的中央区块,而不是ZIP末尾。

这就带来一个问题:Walle往ZIP末尾写渠道信息,会不会破坏V2签名?

答案是:不会。Walle专门做了兼容处理。它把渠道信息写在V2签名的“签名块”之后、ZIP中央目录之前。这个位置是V2签名规范允许的扩展区域。

我曾经踩过的坑:如果你同时使用了V1和V2签名,Walle写入后V1签名会失效。但没关系,Android 7.0以上只校验V2签名。如果你要兼容Android 6.0以下,记得保留V1签名。Walle默认会保留V1签名块,但写入后V1签名会变成“未签名”状态。解决办法是:打包时只使用V2签名,或者用Walle的“增强版”模式。

配置V2签名:

android {
    signingConfigs {
        release {
            v1SigningEnabled true
            v2SigningEnabled true
        }
    }
}

75.6 三种方案对比

方案 打包速度 灵活性 签名兼容 推荐场景
productFlavors 慢(全量编译) 高(可覆盖代码/资源) 完全兼容 渠道间差异大(如不同SDK)
友盟多渠道 慢(全量编译) 中(仅渠道号) 完全兼容 仅需友盟统计
美团Walle 极快(秒级写入) 低(仅渠道号) 需注意V2签名 渠道多、差异小

75.7 我的实战建议

  • 渠道数少于10个:用productFlavors就行,简单直接
  • 渠道数10~50个:用Walle,省时间
  • 渠道数超过50个:必须用Walle,否则打包时间会让你崩溃
  • 渠道间有代码差异:用productFlavors + Walle混合。先用productFlavors打出几个“变体”APK,再用Walle写入具体渠道号

一个小技巧:我习惯在CI/CD流水线里用Walle。编译一次基础APK,然后并行写入所有渠道。整个流程控制在3分钟以内。以前用productFlavors,CI跑一次要40分钟,开发同学经常抱怨“等打包等到下班”。

75.8 知识体系图

多渠道打包方案对比 多渠道打包 productFlavors 全量编译,速度慢 可覆盖代码/资源 签名完全兼容 友盟多渠道 基于productFlavors 仅友盟统计可用 签名完全兼容 美团Walle 秒级写入,极快 仅渠道号,不编译 需注意V2签名 推荐:渠道少用productFlavors,渠道多用Walle

嗯,以上就是多渠道打包的三种主流方案。我个人最常用Walle,但也不排斥productFlavors——看场景。你想想看,如果每个渠道都要换不同的启动页、不同的广告SDK,那还是老老实实用productFlavors吧。


公众号:蓝海资料掘金营,微信deep3321