APK Downloader
返回博客列表
androidapkbuild

APK 多渠道打包原理:四代方案与 Signing Block 工作机制

·10 min read

同一个 App,从官网下的、从应用市场下的、从运营推广链接下的,装到手机上功能一样,但打开后统计到的渠道不同、推送到达的对象不同、部分配置也不同。这背后就是渠道包机制。

这篇讲清三件事:渠道信息怎么塞进 APK、打包工具改了什么、以及为什么老办法在 Android 11 之后会失效。

一句话原理

渠道信息的本质是在 APK 里埋一个可读的标记,应用启动时读出来,上报给统计平台。标记埋在哪儿,决定了打包方式。历史上出现过四代做法,前两代已经不能用了。

第一代:META-INF 空文件(已失效)

最古老也最省事的方式:在 APK 的 META-INF/ 目录下放一个空文件,文件名就是渠道号。

META-INF/
├── MANIFEST.MF
├── CERT.SF
├── CERT.RSA
└── channel_googleplay        ← 空文件,0 字节

打包速度极快——不用重新签名,几秒钟能出一个渠道包,所以美团早期的 packer-ng 就是这么干的。应用启动时遍历 META-INF/ 找 channel_ 前缀的文件,把后缀当渠道号。

为什么失效:Android 11 开始,系统对 APK 签名块做了严格校验,往 META-INF/ 里加文件会破坏 v2/v3 签名校验,安装直接失败。而且这种方式对加固后的 APK 也不适用。

第二代:修改 AndroidManifest 的 meta-data

<meta-data
    android:name="UMENG_CHANNEL"
    android:value="channel_googleplay" />

应用里用 PackageManager 读出来:getApplicationInfo(pkg, GET_META_DATA).metaData.getString("UMENG_CHANNEL")。

这种方式最正统,友盟等统计 SDK 至今仍支持。缺点是每改一次都要重新编译、重新签名,一个渠道包一分多钟,几百个渠道就是几个小时。所以现在基本不用了:太慢,而且无法做增量。

第三代:往 APK Signing Block 写(主流方案)

Android 7.0 引入了 APK Signature Scheme v2。v2 签名会在 ZIP 的中央目录之前插入一个 APK Signing Block,里面除了签名数据,还有一块 ID-value 区域,允许写入自定义数据。

关键点:v2 签名校验时,签名覆盖的是“ZIP 条目内容”,这个 Signing Block 本身被排除在签名范围之外。所以可以往里面加东西而不破坏签名。

[ ZIP 条目区 ]
[ APK Signing Block
    ├─ 长度 + Magic "APK Sig Block 42"
    ├─ v2 签名块 (ID 0x7109871a)
    ├─ v3 签名块 (ID 0xf05368c0)
    └─ 自定义块  (ID 0x71777777)  ← 渠道信息写在这里
]
[ ZIP 中央目录 ]
[ EOCD ]

腾讯的 VasDolly、美团的 walle 都是这个思路。速度非常快——出一个渠道包只需要往 Signing Block 里写几十字节,不重新压缩、不重新签名,一秒能出几十个包。应用侧读取需要 native 代码,因为要解析 ZIP 结构,Java 层的 ZipFile 不方便定位 Signing Block,主流实现都是通过 JNI 读再缓存结果。

注意 v3:v3 签名为了支持密钥轮换,对整个 APK(含 Signing Block)加了额外的完整性保护。VasDolly 等工具已适配,但自研的脚本直接照搬 v2 时代的偏移量计算会失败。判断依据是 APK 里有没有 ID 为 0xf05368c0 的块。

第四代:应用外的渠道归因(新趋势)

近两年越来越多团队干脆不在包里埋渠道,而是走归因:

  • Google Play Install Referrer:从商店安装时,商店会把来源信息写进 referrer,首次启动时由 Play 服务返回。缺点是对 Google Play 之外的渠道无效。
  • 国内应用市场的渠道回调:华为、小米、OPPO 等市场的安装接口会带回渠道标识。
  • 推广链接 + 服务端归因:点击链接时先落到落地页,拿到设备指纹或写入 id,安装后首次启动与服务端比对。

好处是只出一个包,不用打包,也不用维护几十个渠道包文件。坏处是归因有精度损失,而且强依赖平台配合。

实际怎么选

  • 国内分发,渠道几十到几百个:APK Signing Block 方案(VasDolly / walle)
  • 既要上架商店也要走自有渠道:Signing Block + 商店 Install Referrer 组合
  • 加固过的 APK:必须用支持加固的打包工具,或改用归因方案
  • 渠道数很少(小于 10 个):直接改 manifest 重新编译,维护成本最低
  • 海外为主:优先考虑 Install Referrer 归因,减少包管理成本

三个容易踩的坑

坑一:渠道信息在卸载重装后会读错。某些打包工具会把渠道写进设备的外部存储缓存,重装后读到旧的。渠道值必须在每次冷启动时从 APK 本体读取,不要跨安装持久化。

坑二:增量更新后渠道丢失。做 APK 差分更新时,新包如果是从官方包差出来的,Signing Block 会被替换,渠道信息就没了。渠道注入必须作为更新流程的最后一步。

坑三:不同渠道包签名不一致。如果打包工具在写 Signing Block 时重新签名了,会导致渠道包之间签名不同,用户无法从应用市场版本互相覆盖安装。必须用同一个签名,只写块不重签。

验证渠道是否写对了

# 查看签名方案
apksigner verify --print-certs -v your-app.apk

# 确认 APK 里存在 Signing Block
python3 - <<'EOF'
data = open("your-app.apk","rb").read()
print("magic at", data.rfind(b"APK Sig Block 42"))
EOF

再配合 unzip -l 确认没有往 META-INF/ 里塞东西,基本就能判断渠道方案是否健康。

渠道打包这件事,技术本身不难,难的是在多渠道、多商店、加固、增量更新的约束下保持一致性。选一个成熟工具(VasDolly 是目前维护最勤的),把渠道注入固定成构建流水线的最后一个确定性步骤,比自研脚本省心得多。更多安卓打包与分发的资料在 gptoapk 博客。