AGP 签名流程概述:签名在 APK 构建中的位置和作用

签名这件事,听起来像是打包最后点一下按钮的收尾工作。但我刚开始做 Android 构建时,就因为它吃过亏——有一次上线前打 release 包,死活装不上手机,最后发现是签名配置错了,V1 和 V2 没对齐。从那以后,我对签名流程的每个环节都不敢马虎。

说白了,签名就像 APK 的“身份证”加“防拆封条”。Android 系统用签名来验证 APK 的发布者是谁,以及安装后有没有被篡改。在模块化项目中,签名还会影响模块间依赖校验,甚至影响包体积。

签名在 APK 构建流程中的位置

APK 构建不是一步到位的。我习惯把它拆成三个阶段:编译、打包、签名+对齐。签名就卡在打包完成之后、zipalign 对齐之前。

核心顺序: 编译 → 打包(生成未签名 APK) → apksigner 签名 → zipalign 对齐 → 最终 APK

为什么签名要在对齐之前?因为对齐会调整 APK 内文件的字节偏移,如果先对齐再签名,签名块的位置会被破坏,导致签名失效。这个顺序是 AGP(Android Gradle Plugin)写死的,你改了反而会翻车。我曾经见过团队手写 Gradle Task 调换顺序,结果 CI 上打出来的包全都校验失败。

AGP 默认使用 apksigner 进行签名,支持 V1(JAR 签名)、V2(APK 签名方案)、V3(支持密钥轮换)三种方案。默认情况下,minSdkVersion ≥ 24 时只启用 V2,更低版本会同时打 V1+V2。

签名到底干了什么?

签名不是简单的“盖个章”,它做了三件事:

  1. 身份认证: 用你的私钥对 APK 内容摘要签名,系统用公钥验证是同一个开发者。
  2. 完整性校验: 签名的摘要覆盖 APK 里除了签名块以外的所有字节,如果某个文件被改过,摘要对不上,安装就失败。
  3. 更新约束: 同一个包名必须用相同签名升级,否则系统拒绝覆盖安装。这在模块化项目中尤其要小心——不同模块如果用了不同签名,集成后安装会冲突。

你想想看,这三个作用里,任何一个被绕过,后果都很严重。我见过有人为了减小体积手动删掉 APK 里的 .SF 文件,结果安装时报“签名校验失败”,用户根本装不上。

签名对包体积的影响

这部分正是本课程最关心的。签名不是免费的,它会往 APK 里塞入额外的元数据,直接影响包体积。

签名方案体积增量(典型值)说明
V1 (JAR 签名)约 30KB – 60KB包含 META-INF/MANIFEST.MF、.SF、.RSA 等文件,体积较大
V2 (APK 签名方案)约 10KB – 20KB签名数据嵌入 APK 文件尾部「签名块」区域,更紧凑
V3 (支持轮换)约 15KB – 30KB在 V2 基础上增加了密钥轮换信息,体积略有增加

注意,这些是签名块本身的体积。如果你的模块非常多(比如几十个动态 feature),每个模块独立签名,那累加起来的体积就不可忽视了。我曾在某个大型模块化项目中,因为所有 feature 模块都打了 V1+V2 双重签名,最终包体积比只打 V2 多了近 1MB。

一个常见的误区是:签名证书越大,包体积越大。证书本身(.RSA 或 .DER)通常只有 1-2KB,占大头的是签名过程中的摘要和签名数据。所以更换证书对体积影响微乎其微。

小经验: 如果你的 minSdkVersion ≥ 24(Android 7.0),建议关闭 V1 签名,只保留 V2。这样既能保证安全性,又能节省 20-30KB 体积。配置方法很简单:

android {
    defaultConfig {
        signingConfig signingConfigs.release
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
            // 仅启用 V2 签名
            minifyEnabled true
            v1SigningEnabled false
            v2SigningEnabled true
        }
    }
}

注意: 关闭 V1 签名后,如果用户的手机系统低于 Android 7.0(API 24),将无法安装该 APK。如果你仍需支持老设备,必须保留 V1。用 v1SigningEnabled true 同时启用两者——当然,体积也会翻倍。

签名在模块化项目中的特殊地位

模块化项目里,每个模块打包后可能先产生 .ap_(资源包)或单独签名。AGP 在 assemble 阶段会做两遍签名:

  1. 模块级签名: 每个动态功能模块(dynamic feature)在构建自己的 .apk 时进行一次签名。
  2. 全局重新签名: 基础 APK + 所有 feature APK 合并后,最终基础 APK 会被再次签名(仅 V2 部分覆盖)。

这两次签名会重复写入签名块。如果基础模块和 feature 模块用了不同的签名配置,甚至会导致“签名不一致”的编译错误。我这就有个教训:曾经把 debug 签名和 release 签名混在同一个项目里,assemble 时报错说 feature 包签名不匹配——排查了一下午才发现是 signingConfig 配错了。

SVG 流程图:签名在 APK 构建中的位置

下面这张图能让你一眼看明白,签名处于哪个环节,以及它前后的处理步骤。我建议你把它贴在工位旁边,下次配签名配置时脑子里就有谱了。

APK 构建流程与签名位置 编译 打包(未签名APK) 签名 (V1/V2/V3) zipalign 对齐 最终 APK 签名块平均增 10~60KB 对齐不改变 签名块内容

避坑指南:别让签名偷偷吃掉你的体积

  • 我曾经在 Gradle 里不小心勾选了 v1SigningEnabled true 和 v2SigningEnabled true,然后 minSdkVersion = 26(Android 8.0),结果包体积多了 40KB,毫无意义。如果你的最低版本 ≥ 24,果断关 V1。
  • 注意 debug 签名 vs release 签名: debug 签名默认使用 Android Studio 生成的调试证书,体积很小。但 release 签名如果是自己生成的 .jks 文件,大小差别不大。关键是别把 debug 签名用于线上发布——那是基础安全常识。
  • 模块化项目里,统一签名配置: 把所有模块的 signingConfigs 抽取到公共脚本,避免每个模块单独定义。否则可能某个 feature 模块用了老的 V1+V2 配置,导致基础 APK 升级时签名冲突。