目录

Android 多渠道打包

DakingTech 原创不易,转载请注明出处

渠道包

渠道包就是要在安装包中添加渠道信息,即 channel,对应不同的渠道。

为每个安装包设定一个可以区分渠道的标识的过程就是多渠道打包。

国内存在着众多的应用市场,产品在不同的渠道可能有不同的业务需求,因此,Android App 开发者需要为每个应用市场发布一个对应的安装包,其中包含对应的渠道信息(channel 值)。 App 运行时根据当前的 channel 值进行对应渠道的业务。

多渠道打包与 APK 签名

多渠道打包的切入点原则:附加信息不能影响 APK 签名的验证。

对于 V1 签名得到的 apk,可以在其 META-INF 文件夹下添加一个空文件,用此空文件的名称来作为渠道标识。

在 V2 签名机制下,如果像之前 V1 签名那样在 META-INF 文件夹下添加渠道标识文件,那样会对 apk 中的 Contents of ZIP entries、Central Directory 和 End of Central Directory 等数据区块都产生影响,从而导致 apk 签名值与 APK Signing Block 中记录的签名值不一致。

其实,V2 签名的 APK Signing Block 不是全部参与签名校验,只有其记录 apk 签名信息的部分参与校验,于是,对于 V2 签名得到的 apk,可以在 APK Signing Block 中添加一个额外的 ID 值来作为渠道标识。

通过 Gradle 实现多渠道打包

此方式的核心原理是:在编译时通过 Gradle 修改 AndroidManifest.xml 中的 meta-data 占位符的内容,执行 N 次打包流程,每次输出对应某个渠道的安装包。

Gradle 构建中与渠道相关的几个概念:

  • BuildType:构建类型,用于定义一个产品的不同开发周期,比如 debug、release
  • ProductFlavor:产品风味,用于定义多个不同的产品,比如 huawei、xiaomi
  • BuildVariant:构建变体,为 BuildType 和 ProductFlavor 的笛卡尔积

首先,在 AndroidManifest.xml 中定义用于标识渠道的 meta-data 占位符 ${CHANNEL_VALUE}

1
2
3
4
5
<application>
    <meta-data>
        android:name="CHANNEL"
        android:value="${CHANNEL_VALUE}" />
</application>

接着,在 app 模块的 build.gradle 中定义各 ProductFlavor,比如 xiaomi、huawei。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
andorid {
    flavorDimensions "dafault"

    productFlavors {
        xiaomi {
            // 可以对该渠道做一些定制
            applicationId "tech.daking.app.channel.xiaomi"
            manifestPlaceholders.put("BASE_URL_VALUE", "https://www.mi.com/")
        }
        huawei {
        }
    }
}

然后,遍历各 Flavor 并用其名称 flavor.name 来填充其渠道包对应的 meta-data 占位符 ${CHANNEL_VALUE}

1
2
3
4
android.productFlavors.all { flavor ->
    println("flavor.name: ${flavor.name}")
    manifestPlaceholders.put("CHANNEL_VALUE", flavor.name)
}

最后,在代码中读取 meta-data 占位符 ${CHANNEL_VALUE} 就知晓当前 apk 包的渠道标识。

1
2
3
4
5
6
7
8
fun Context.getAppMetaData(key: String): String {
    val appInfo = this.packageManager.getApplicationInfo(
        this.packageName, PackageManager.GET_META_DATA
    )
    return appInfo.metaData.getString(key) ?: ""
}

val channel = context.getAppMetaData("CHANNEL")

此方式支持 V1 和 V2 签名的 apk。

此方式存在一些缺点:

  • 每生成一个渠道包都要重新执行一遍构建打包流程,效率过低,只适用于渠道较少的场景。
  • Gradle 会为每个渠道包生成一个不同的 BuildConfig.java 类,其中的 FLAVOR 值不同,这就导致每个渠道包的 dex 的 CRC 值不同。
1
2
3
4
5
public final class BuildConfig {
    ,..
    public static final String FLAVOR = "xiaomi";
    ...
}

如果使用了微信的 Tinker 热修复方案,那么就需要为不同的渠道包打不同的补丁。

Tinker 是通过对比基础包 base.apk 和新包 new.apk 生成差分补丁 patch.apk,然后再把补丁 patch.apk 和已安装的基础包 old_base.apk 一起合成新的 apk。

这就要求用于生成差分补丁的基础包 base.apk 中的 dex 和已安装的基础包 old_base.apk 中的 dex 是完全一致的。即如果手机上安装的是小米渠道包,那必须要使用小米渠道包来生成补丁 patch.apk。

通过 ApkTool 实现多渠道打包

ApkTool 是一个逆向分析工具,可以解开 apk 和重新打包 apk。

通过 ApkTool 实现多渠道打包的步骤:

  1. 利用 ApkTool 对 apk 进行解包
  2. 删除已有的签名信息
  3. 添加渠道信息,比如:在 META-INF 目录下添加一个名称为渠道标识的空文件
  4. 利用 ApkTool 重新打包生成新的 apk
  5. 对 apk 重新进行签名

此方式每生成一个渠道包,都需要经历解包、重新打包和签名的过程,而这几步操作又是相对机械且耗时的。

此方式常用于对 V1 签名的 apk 进行多渠道打包。

多渠道打包的自动化方案

方案 腾讯 vasDolly 美团 walle packer-ng-plugin
V1 签名 支持 不支持 支持
V2 签名 支持 支持 支持
根据已有包生成渠道包 支持 不支持 不支持
多线程打包加速 支持 不支持 不支持
签名校验 支持 支持 支持

总结

在实际开发中,一般是通过 Gradle 或自动化方案来进行多渠道打包。

参考资料

移动端架构师-慕课网