本文介绍如何基于火山引擎内容分发网络(CDN)的 APK 动态打包 能力,在源站只保留一个 APK 文件的前提下,由 CDN 根据每次下载请求携带的参数,在边缘节点实时生成带有不同附加信息的安装包并返回给用户。通过这种方式,您无需为每一种附加信息(例如不同的推广渠道标识)单独打包一份 APK,即可显著降低源站的存储和维护成本。
一个标准的 APK 文件在完成 Android V2 或 V3 签名后,其主体内容(代码、资源、签名等)已不可修改,但 APK 签名块(APK Signing Block)中保留了可供第三方写入自定义信息的扩展位。传统方案通常在打包阶段将附加信息写入 APK,再把生成的多份 APK 上传到源站进行分发,由此带来以下几方面问题:
火山引擎 CDN 的 APK 动态打包方案将附加信息写入的时机从打包阶段后置到 CDN 分发阶段:源站只保留一份未写入任何附加信息的 APK 文件(下文称 母包),当终端请求到达 CDN 边缘节点时,CDN 根据请求 URL 中约定的查询参数,在不改动 APK 主体内容和签名的前提下,动态地将参数值写入母包的签名块扩展位,并将生成的 APK 返回给终端。
说明
APK 动态打包是火山引擎 CDN 的增值能力,需要您在使用前提前 提交工单 申请开通。
优势 | 说明 |
|---|---|
源站无需改造 | 源站只保留一份母包,附加信息的写入由 CDN 在边缘节点完成。 |
不绑定对象存储 | 写入过程完全在 CDN 侧完成,不要求源站使用火山引擎或任意第三方的对象存储,源站可以是任何可通过公网访问的服务。 |
APK 完整性不受影响 | 方案基于业内成熟的开源项目 Walle 实现,附加信息仅写入 APK 签名块中的扩展位,不影响 APK 主体内容和签名校验,安装体验与常规 APK 一致。 |
缓存命中率高 | CDN 仅缓存一份母包,所有带不同附加信息的请求都共用这一份缓存,不会因附加信息取值不同而产生多份独立缓存或重复回源。 |
处理时延低 | 动态写入过程为毫秒级,对终端用户的下载体验几乎无感。 |
开启过程无感知 | 在已有域名上开启 APK 动态打包能力时,已缓存的 APK 母包不会被清除,线上正在进行的 APK 下载业务不受影响,可以随时开启,无需停服或安排专门的上线变更时间。 |

方案整体工作流程如下:
comment 参数,其值为自定义的 JSON 字符串。用户点击该链接即可向 CDN 发起下载请求。comment 参数值并将其写入缓存中母包的签名块扩展位,将动态生成的 APK 返回给用户。若母包本身已存在历史附加信息,将被本次请求的新值直接覆盖。示例:
假设原始 APK 的下载地址为 http://www.volcengine.com/test.apk,需要为某一次投放写入如下附加信息:
{"channel":"baiduqk","resourceType":"song","resourceValue":"483937795"}
将上述 JSON 进行 URL 编码后作为 comment 参数的值拼接到 APK 下载地址后,完整的下载链接如下:
http://www.volcengine.com/test.apk?comment=%7B%22channel%22%3A%22baiduqk%22%2C%22resourceType%22%3A%22song%22%2C%22resourceValue%22%3A%22483937795%22%7D
CDN 收到该请求后,会将 comment 参数的 JSON 内容写入 test.apk 的签名块扩展位,然后返回处理后的 APK。App 安装或启动时即可解析出该附加信息,用于后续的业务分析。
comment 查询参数(参数值为您自定义的 JSON 字符串,用于标识渠道或其他附加信息)。APK 动态打包功能按加速域名单独开启,使用前需提前申请开通。
comment,不支持自定义。将编译完成的 APK 母包上传至源站。注意事项:
comment 的值直接覆盖。为避免首次请求回源引入额外时延,建议在业务上线前对母包进行一次预热:
comment 参数 您的投放系统、官网下载按钮、推广落地页等生成 APK 下载链接的地方,在生成下载链接时,按以下步骤拼接 comment 参数:
{"channel":"baiduqk"}
comment 参数的值拼接到 APK 下载链接后。例如:https://apks.example.com/test.apk?comment=%7B%22channel%22%3A%22baiduqk%22%7D
注意
comment,不支持改成其他名称。comment 的值,避免超出签名块扩展位的容量上限。comment 值进行合法性校验,请在生成下载链接前完成校验。业务上线后,建议按以下方式验证效果:
构造几组 comment 参数值不同的下载链接,分别下载对应的 APK(建议在不同的测试设备或网络环境下各下载一次,避免被本地缓存干扰)。
对下载得到的 APK 使用 Walle 命令行工具 或自研的解析工具,读取 APK 中写入的附加信息,确认其内容与下载链接中的 comment 值一致。
使用 curl 等工具请求同一个 APK 的不同 comment 下载链接,查看响应头中的 X-Bdcdn-Cache-Status 字段,确认所有请求都是命中缓存(值包含 TCP_HIT),以此验证不同 comment 取值的请求共用的是同一份母包缓存、没有各自回源。示例:
curl -I "https://apks.example.com/test.apk?comment=%7B%22channel%22%3A%22huawei%22%7D" curl -I "https://apks.example.com/test.apk?comment=%7B%22channel%22%3A%22baidu%22%7D"
在返回的响应头中,两次请求的 X-Bdcdn-Cache-Status 均应包含 TCP_HIT。
为充分发挥 APK 动态打包方案的效果,建议同步完成以下配置:
comment 参数从缓存 Key 中忽略。这样不同取值的下载请求可命中同一份母包缓存,提升命中率并降低回源压力。.apk 文件配置较长的缓存时间(例如 30 天以上),避免母包被频繁回源。不会。该方案将附加信息写入的是 APK 签名块中用于存放扩展信息的位置(Android 官方允许)。Android 系统在安装 APK 时只会校验签名本身,不会校验该扩展位的内容,因此 APK 签名依然有效,终端用户可正常安装。
在源站替换为新版母包后,建议通过 缓存刷新 功能刷新对应 APK 的 URL。CDN 在收到刷新指令后,下一次用户请求将回源拉取最新的母包并重新对外分发。
不支持。目前用于携带附加信息的参数名固定为 comment。如需同时传递多个维度的信息,建议将所有信息组织为一个 JSON 对象,作为 comment 参数的值整体传递。