Google Play 上架与签名打包

打包上架是每个 Android 项目的最后一公里,签名与合规细节出错就会卡住发布。本文讲透上架与签名打包:密钥库生成与应用签名密钥的分工、分发包格式差异、混淆与映射文件上传、版本号策略与分阶段发布,以及后台的数据安全表单、目标版本适配与持续集成自动发布。文中附打包工具的本地验证流程与上架常见坑清单,帮你顺利过审并稳定迭代。

打包上架看起来只是「点一下 Build 再上传」,但真正踩过坑的人都知道:签名配错一次密钥就永久丢失、versionCode 冲突导致上传被拒、mapping 没上传导致崩溃栈全是 a.b.c、targetSdk 没跟上直接被商店下架。这些问题的共同点是一旦发生就不可逆。

本文按「先搞懂签名,再打通流程,最后自动化」的顺序展开:先讲密钥体系与 Play App Signing,再讲 AAB、版本号与上架流程,最后给出 R8 mapping、bundletool、Gradle Play Publisher 与 targetSdk 适配的实践清单。

一、签名体系:keystore 与签名配置

Android 用签名来证明「这个包确实来自同一个开发者」。同一应用的所有版本必须用同一把密钥签名,否则系统会拒绝安装升级。

keytool -genkeypair -v \
  -keystore release.jks \
  -alias myapp \
  -keyalg RSA -keysize 4096 -validity 10000 \
  -storetype JKS   # 交互式输入密码与证书信息,有效期 10000 天
android {
    signingConfigs {
        create("release") {
            // 密钥信息从环境变量读取,绝不提交到仓库
            storeFile = file(System.getenv("KEYSTORE_PATH") ?: "release.jks")
            storePassword = System.getenv("KEYSTORE_PASSWORD")
            keyAlias = System.getenv("KEY_ALIAS")
            keyPassword = System.getenv("KEY_PASSWORD")
        }
    }
    buildTypes {
        release {
            isMinifyEnabled = true
            isShrinkResources = true
            signingConfig = signingConfigs.getByName("release")
        }
    }
}
项说明注意
keystore 文件存放密钥的容器丢失后无法再发布同名应用更新
alias密钥别名一个 keystore 可存多把密钥
有效期-validity 天数Play 要求至少到 2033 年
签名方案v1 到 v4v2 起支持整包校验,v3 支持密钥轮换
密码管理环境变量或 CI Secret禁止硬编码进 build.gradle.kts

一句话总结: 签名的铁律是**「同一应用永远用同一把密钥,且密钥与密码绝不进版本库」**;keystore 一旦丢失,该应用在 Play 上就再也无法发布更新。


二、Play App Signing:上传密钥与应用签名密钥

2017 年后 Google 引入 Play App Signing,把「应用签名密钥」托管在 Google 侧,开发者只持有「上传密钥」。这是新应用的默认方案,也是强烈推荐的方案。

概念持有方作用可否轮换
应用签名密钥Google 托管真正给用户安装的包签名可申请轮换,需审核
上传密钥开发者给上传到 Play 的 AAB 签名可自行轮换,无需审核
本地 keystore开发者仅用于本地调试与自签名分发随意
路径 A(推荐):新应用直接用 Play App Signing
  1. Play Console 创建应用时选择「使用 Google 管理密钥」
  2. 上传用上传密钥签名的 AAB,Google 用应用签名密钥重签后分发

路径 B(存量应用迁移):
  1. 生成新的上传密钥,导出现有应用签名密钥
  2. 在 Play Console 提交密钥迁移申请,之后用上传密钥签名所有版本

关键收益:上传密钥泄露可立刻轮换而不失去更新能力,
          应用签名密钥由 Google 保管,不会因本地硬盘损坏而丢失

一句话总结: Play App Signing 把「不可轮换的应用签名密钥」与「可轮换的上传密钥」分离,让密钥泄露从「应用报废」降级为「换一把密钥」;新应用没有理由不用它。


三、AAB 与 APK 的差异

Google Play 自 2021 年 8 月起要求新应用使用 AAB(Android App Bundle)上传。AAB 不是安装包,而是一份「包含所有代码与资源的构建产物」,由 Play 按用户设备配置生成对应的 APK 下发。

维度APKAAB
形态可直接安装的包构建产物,不可直接安装
分发方式直接上传或第三方渠道上传 Play,由 Play 生成 APK
体积包含所有密度与 ABI 资源按设备拆分,用户下载更小
动态交付不支持支持 Play Feature Delivery
本地验证直接 adb install需 bundletool 生成 APK 集
签名开发者签名上传密钥签名,Play 重签
./gradlew :app:bundleRelease        # 产出 app/build/outputs/bundle/release/app-release.aab
./gradlew :app:assembleRelease      # 产出 APK,用于第三方渠道或本地验证

对于国内的第三方应用商店或企业分发,仍然需要 APK;因此同时产出 AAB 与 APK 是常见做法,两者共用同一份 signingConfigs。

一句话总结: AAB 是「上传格式」,APK 是「安装格式」;上架 Google Play 用 AAB 换取更小的下载体积与动态交付能力,第三方渠道继续用 APK。


四、versionCode 与 versionName 策略

versionCode 是给系统看的整数,必须单调递增;versionName 是给用户看的字符串,可任意格式。两者都在 defaultConfig 中声明。

android {
    defaultConfig {
        versionCode = 42            // 每次发布必须严格递增,Play 拒绝重复
        versionName = "2.4.0"       // 面向用户的语义化版本
    }
}

// 常见做法:用 CI 构建号自动生成 versionCode,避免人工冲突
val buildNumber = (System.getenv("BUILD_NUMBER") ?: "1").toInt()
android.defaultConfig.versionCode = 10000 + buildNumber
方案versionCode 生成方式优点风险
手工递增每次发布前手动改直观可控多人协作易冲突或回退
CI 构建号10000 + BUILD_NUMBER全局唯一、自动递增需保证构建号单调
时间戳YYMMDDnn天然递增位数增长快,接近上限
版本号映射major*10000 + minor*100 + patch与 versionName 对应大版本超过 2 位数会溢出

versionCode 上限是 2100000000,约 21 亿,正常项目远远够用,但绝不能回退:一旦上传了 versionCode = 42,之后所有渠道的版本都必须大于 42。

一句话总结: versionCode 必须单调递增且全局唯一,versionName 随意但要与用户认知一致;把它交给 CI 用构建号生成,是避免「上传被拒」最省心的做法。


五、Play Console 上架流程

上架不是上传文件就结束,而是一组必填的合规表单与审核环节。首次上架的完整链路如下:

首次上架步骤:
  1. 创建应用:填写应用名、默认语言、应用类型(应用/游戏)、免费或付费
  2. 设置商品详情:应用图标(512x512)、特色图片(1024x500)、截图、描述
  3. 完成内容分级问卷:IARC 问卷,决定分级标签
  4. 填写数据安全表单:声明收集哪些数据、是否加密传输、是否可删除
  5. 声明目标受众与内容:是否面向儿童,是否含广告
  6. 上传 AAB 到测试轨道,验证后再推进到正式轨道并提交审核
表单关键点常见驳回原因
内容分级IARC 问卷必须与真实内容一致声明与实际内容不符
数据安全必须覆盖所有 SDK 的数据收集行为遗漏第三方 SDK 收集项
目标受众面向儿童需符合 Families 政策儿童应用含未过滤广告
隐私政策所有收集数据的应用都必须提供 URL链接失效或内容空泛
广告声明含广告必须勾选未声明导致下架
权限说明敏感权限需提供使用场景说明权限与功能不匹配
关于「目标 API 级别」的硬要求:
  Play 要求新应用与更新每年跟进 targetSdk
  例如 2024 年 8 月 31 日起新应用需 targetSdk 34
  2025 年 8 月 31 日起新应用需 targetSdk 35
  未跟进的应用会被拒绝更新,存量应用可能被限制可见性

自查命令:检查构建产物中声明的 targetSdk
  ./gradlew :app:assembleRelease
  aapt2 dump badging app/build/outputs/apk/release/app-release.apk | grep targetSdkVersion

一句话总结: 上架的难点不在技术,而在**「数据安全表单、内容分级、隐私政策」这三张合规答卷**;填错或遗漏的代价是审核驳回甚至下架,因此务必逐项核对每个第三方 SDK 的数据行为。


六、发布轨道与分阶段发布

Play 提供多条发布轨道,让新版本先小范围验证,再逐步放量。

轨道面向对象审核适用场景
内部测试(internal)最多 100 个指定邮箱通常最快每日构建、快速验证
封闭测试(closed)邮件列表或 Google Group需要审核小范围灰度、beta 用户
开放测试(open)所有人可加入需要审核大范围公测
正式(production)全部用户需要审核正式发布
分阶段发布按百分比放量同正式降低线上事故影响
分阶段发布的典型节奏:
  第 1 天 1% 用户 → 看崩溃率与 ANR 率
  第 2 天 5% 用户 → 看核心指标与用户反馈
  第 3 天 20% 用户 → 看留存与转化
  第 4 天 50% 用户 → 第 5 天 100% 用户

任何一步异常都可以「暂停发布」或「回滚到上一版本」:
  暂停:停止继续放量,已升级用户不受影响
  回滚:需上传 versionCode 更高的旧功能版本,不能直接降级

注意「回滚」在 Play 上并不等于降级:你不能让用户装回旧版本,只能发布一个 versionCode 更大的「功能等价于旧版」的新版本。因此分阶段发布期间的崩溃监控比什么都重要,上线前先建立性能与崩溃基线,才能判断放量是否安全。

一句话总结: 内部轨道用于每日验证,分阶段发布用于降低事故半径;但要记住 Play 不支持真正降级,所谓回滚是用更高 versionCode 重新发一个旧功能版本。


七、R8 混淆与 mapping 上传

release 构建开启 R8 后,类名与方法名会被重命名,崩溃栈里的 com.example.Foo.bar 变成 a.b.c。要还原它,必须把 mapping.txt 上传到崩溃监控平台或 Play Console。

android {
    buildTypes {
        release {
            isMinifyEnabled = true
            isShrinkResources = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
}
app/build/outputs/mapping/release/mapping.txt                    # mapping 文件位置
retrace app/build/outputs/mapping/release/mapping.txt crash.txt  # 用 retrace 还原崩溃栈
方式操作适用
Play Console 上传发布时勾选「上传去混淆文件」依赖 Play 的崩溃报告
mappingFileUploadAGP DSL 自动上传自动化流水线
CI 归档把 mapping 与版本号一起存对象存储自有崩溃平台
retrace 命令行手动还原单条崩溃栈排查个案
// AGP 8.x:自动上传 mapping 到 Play(需配置 Play 凭据)
android {
    buildTypes {
        release {
            // 与 Gradle Play Publisher 配合时通常由 GPP 处理
            // 单独使用时可通过 play 服务账号自动上传
        }
    }
}

mapping 文件必须与版本严格对应:一旦丢失,该版本的线上崩溃就永久无法还原。因此 CI 中应把 mapping.txt 作为构建产物归档,并带上 versionCode 命名。

一句话总结: mapping.txt 是 release 崩溃可读的唯一钥匙;把它与 versionCode 绑定归档,并在发布流程里自动上传,否则 R8 的收益会被「崩溃无法定位」抵消。


八、bundletool 本地验证

AAB 不能直接安装,本地验证需要 bundletool 把它转换成 APK 集或直接部署到设备。

bundletool build-apks --bundle=app-release.aab --output=app.apks \
  --ks=release.jks --ks-key-alias=myapp        # 生成 APK 集(含所有拆分的 APK)
bundletool install-apks --apks=app.apks        # 按设备配置安装,模拟 Play 的分发行为
bundletool get-size total --apks=app.apks --dimensions=ABI,SCREEN_DENSITY   # 估算下载体积
命令作用场景
build-apksAAB 转 APK 集本地安装验证
install-apks按设备配置安装真机回归
get-size total估算各维度下载体积体积优化
dump manifest查看合并后的 manifest权限与 SDK 核查
为什么必须本地验证:
  AAB 的拆分逻辑只在 Play 侧生效,本地 assembleRelease 出来的 APK
  与用户实际下载的 APK 不是同一份,典型问题只会在某个 ABI 或密度下暴露
  验证方式:用 install-apks 装到真机,或 get-size 看拆分是否符合预期

一句话总结: AAB 必须用 bundletool 转成 APK 才能本地验证,install-apks 能复现 Play 的拆分分发行为,是发现「特定机型资源缺失」最有效的手段。


九、CI 自动发布:Gradle Play Publisher

手动上传容易出错且不可追溯,com.github.triplet.play 插件(Gradle Play Publisher,GPP)让发布变成一条 Gradle 任务。当前主流版本是 3.12.x。

// app/build.gradle.kts
plugins {
    id("com.android.application")
    id("com.github.triplet.play") version "3.12.0"
}

play {
    // 服务账号 JSON,来自 Play Console 的 API 访问设置
    serviceAccountCredentials.set(file(System.getenv("PLAY_JSON_KEY") ?: "play-key.json"))
    track.set("internal")            // 目标轨道:internal / beta / production
    defaultToAppBundles.set(true)    // 默认上传 AAB
    userFraction.set(0.1)            // 分阶段发布:按百分比放量
    resolutionStrategy.set(com.github.triplet.gradle.play.ResolutionStrategy.AUTO)  // 自动递增版本号
}
./gradlew :app:publishReleaseBundle            # 发布到配置的轨道
./gradlew :app:publishReleaseBundle --track production --user-fraction 0.05   # 5% 分阶段
./gradlew :app:publishListing                  # 只更新商品详情,不发新版本
配置项作用建议
serviceAccountCredentialsPlay API 凭据存 CI Secret,禁止入库
track目标轨道先 internal,验证后推 production
defaultToAppBundles上传 AAB 而非 APK设为 true
userFraction分阶段放量比例首次发布用 0.05 到 0.1
resolutionStrategy版本号处理策略AUTO 自动递增 versionCode
CI 流水线中的发布步骤:
  1. 拉取代码并检出发布 tag
  2. 用 CI Secret 注入 keystore 与 Play 服务账号 JSON
  3. ./gradlew :app:bundleRelease 构建 AAB
  4. ./gradlew :app:publishReleaseBundle --track internal 先发内测
  5. 归档 mapping.txt 与 AAB 到对象存储
  6. 人工确认后推进到 production 并设置 userFraction

注意:GPP 会调用 Play Developer API,服务账号需在 Play Console
      中授予「发布到测试轨道」与「管理正式版发布」权限。

一句话总结: GPP 把「构建 AAB + 上传 + 设置轨道与放量比例」收敛成一条 Gradle 任务,配合 CI Secret 与归档 mapping,才让发布过程可追溯、可回放。


十、targetSdk 适配与隐私合规

targetSdk 决定系统以哪个版本的行为兼容模式运行你的应用,每年跟进是硬要求。近年变化集中在权限、后台行为与数据访问。

targetSdk关键变化适配要点
33通知需运行时权限、细分媒体权限请求 POST_NOTIFICATIONS,改用 READ_MEDIA_IMAGES
34前台服务类型必填、隐式 Intent 限制声明 foregroundServiceType,显式 Intent
35边到边强制、后台启动限制收紧处理 WindowInsets,避免后台启动 Activity
通用精确闹钟、剪贴板、位置权限按需申请,提供使用场景说明
// targetSdk 33 起:通知需要运行时权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    launcher.launch(Manifest.permission.POST_NOTIFICATIONS)
}

// targetSdk 33 起:读取图片用细分权限替代 READ_EXTERNAL_STORAGE
val imagePermission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    Manifest.permission.READ_MEDIA_IMAGES
} else {
    Manifest.permission.READ_EXTERNAL_STORAGE
}
<!-- targetSdk 34 起:前台服务必须声明类型 -->
<service
    android:name=".SyncService"
    android:foregroundServiceType="dataSync"
    android:exported="false" />

隐私合规方面,除了数据安全表单,还要注意 SDK 的数据收集声明与「同意管理」:面向欧洲经济区用户的应用需要 Google 认证的 CMP(Consent Management Platform),并在收集广告标识符前获取同意。

一句话总结: targetSdk 跟进是「不做就下架」的硬约束,隐私合规是「做了但不填表也会被拒」的软约束;两者都要在发版清单里逐项核对,而不是等审核驳回再补。


十一、常见坑清单

坑现象对策
keystore 丢失无法再发布该应用更新用 Play App Signing,本地备份 keystore 与密码
密码硬编码密钥随仓库泄露改用 CI Secret 或环境变量
versionCode 重复或回退上传被拒交给 CI 用构建号生成并保证单调
忘记上传 mapping崩溃栈不可读发布流程中归档并上传 mapping.txt
只测 APK 不测 AAB特定机型资源缺失用 bundletool install-apks 真机验证
数据安全表单漏填审核驳回或下架逐个第三方 SDK 核对数据行为
targetSdk 未跟进无法更新或限制可见性每年 8 月前完成适配
分阶段发布当成降级以为可以回滚旧版用更高 versionCode 重发旧功能版本
凭据权限不足GPP 报 403服务账号授予发布与正式版管理权限

一句话总结: 上架坑的本质是**「密钥不可再生、版本号不可回退、合规不可含糊」**三条不可逆约束;把这三条写进发布 checklist 并自动化,比任何事后补救都有效。


小结

主题关键结论
签名同一应用同一密钥,密钥与密码绝不入库
Play App Signing上传密钥可轮换,应用签名密钥由 Google 托管
AAB 与 APKAAB 是上传格式,APK 是安装格式,两者共用签名配置
版本号versionCode 单调递增,交给 CI 生成
上架流程内容分级、数据安全、隐私政策三张答卷必须逐项核对
发布轨道内测验证、分阶段放量,Play 不支持真正降级
mapping与 versionCode 绑定归档并自动上传
本地验证bundletool install-apks 复现 Play 的拆分分发
自动化与 targetSdkGPP 3.12.x 收敛发布流程,targetSdk 每年跟进

一句话记住:上架流程的核心是「不可逆约束的自动化」——密钥不丢、版本号不乱、mapping 不丢、合规不漏,这四件事都靠流程与 CI 保证,而不是靠记忆。发布之后,真正的考验才刚开始:线上崩溃率、启动耗时与帧率都会影响留存,性能基线可以沿用 Android 性能优化与启动加速 里的测量方法;而版本迭代越快,模块边界与构建耗时越会成为瓶颈,可参考 Android 多模块架构与 Gradle 优化 ,依赖注入层的拆分思路则见 Hilt 依赖注入实践 。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「Android 开发」更多文章

  1. Kotlin Multiplatform 跨平台共享
  2. Android R8 混淆与 Baseline Profile
  3. Android 测试体系:单元测试、Espresso 与 Compose 测试