Flutter Android 正式签名实战:生成密钥、配置构建与验证 APK
Flutter Android 正式签名实战:生成密钥、配置构建与验证 APK
Android 正式签名不需要购买证书。开发者可以自己生成密钥,用它给 APK 签名;密钥的用途是建立稳定的应用身份。签名有效并不等于应用已经通过商店审核,也不等于应用功能已完成真机验收。
本文面向 Flutter Android 项目,介绍从开发签名切换到独立正式签名的完整流程。应用名称、目录、包名和证书信息均使用通用示例,不包含真实项目密码、私钥、签名指纹或账号数据。
一、先理解三个不同概念
| 概念 | 作用 |
|---|---|
应用标识 applicationId | Android 用来区分安装在设备上的应用 |
| 签名私钥与证书 | 私钥用于签名,证书用于验证签名身份 |
| 证书指纹 SHA-256 / SHA-1 | 证书摘要,常用于核对身份或配置第三方服务 |
证书指纹不是签名密码,也不能用来还原私钥。可以分享公开证书,但不能把 keystore、私钥或密码放到公开文章中。签名密钥与证书
同一包名和兼容签名是覆盖升级的基本条件。仅仅换了签名,不会自动多出一个应用;包名相同但证书不兼容时,通常会安装失败。包名不同则可以同时安装,即使名称和图标完全相同。应用标识规则
二、生成一套长期使用的正式密钥
如果项目已有正式签名,先恢复并沿用已有密钥,不要重新生成。下面只适用于首次创建。
keytool 随 JDK 提供。以下示例在 macOS / Linux 上把签名材料放到代码仓库外:
APP_SIGNING_DIR="$HOME/.config/example-app/signing"
mkdir -p "$APP_SIGNING_DIR"
chmod 700 "$APP_SIGNING_DIR"
keytool -genkeypair \
-keystore "$APP_SIGNING_DIR/app-release.jks" \
-storetype JKS \
-alias release \
-keyalg RSA \
-keysize 3072 \
-sigalg SHA256withRSA \
-validity 10000 \
-dname "CN=Example App Release"
chmod 600 "$APP_SIGNING_DIR/app-release.jks"
按交互提示设置强密码,不把真实密码写进命令参数或复制到文章中。这里的证书名称是示例;实际名称不需要伪造个人或机构身份。10000 表示有效期天数,约 27 年;RSA 3072 是此示例的算法选择,不是 Android 唯一允许的配置。keytool 参数说明
生成后保存四项信息:keystore 文件、keystore 密码、key alias,以及私钥密码。可以在工具支持的前提下使用同一个强密码,但仍要了解两项密码的用途。后续版本继续使用这套密钥,而不是每次打包都生成一份新的。
三、让 Flutter 的 release 构建使用正式签名
1. 创建本地签名配置
在签名材料目录中创建 key.properties,把占位内容替换成实际值。storeFile 必须是实际绝对路径,不要照抄示例路径:
storePassword=YOUR_STORE_PASSWORD
keyPassword=YOUR_KEY_PASSWORD
keyAlias=release
storeFile=/absolute/path/to/app-release.jks
Java Properties 对反斜杠等字符有转义规则;Windows 路径尤其要注意。不要给实际密码随意加引号,否则引号可能成为密码的一部分。Properties 文件格式
macOS / Linux 可以通过本地链接把配置提供给项目,在项目根目录执行:
chmod 600 "$APP_SIGNING_DIR/key.properties"
ln -s "$APP_SIGNING_DIR/key.properties" android/key.properties
如果 android/key.properties 已存在,先核对它,不要直接覆盖。也可以使用普通本地文件,前提是忽略 Git 并保护访问权限。权限 600 和 700 限制同机其他用户访问,但不能代替加密备份。
在 android/.gitignore 中加入:
key.properties
**/*.jks
**/*.keystore
团队需要共享配置结构时,只提交一个包含占位值的 key.properties.example。如果秘密以前已经被 Git 跟踪,补一条忽略规则并不能消除历史泄露。
2. 修改 Kotlin Gradle 配置
在 android/app/build.gradle.kts 顶部增加导入:
import java.util.Properties
在现有 plugins 块之后、android 块之前加载配置:
val releaseValues = Properties()
val releaseValuesFile = rootProject.file("key.properties")
if (releaseValuesFile.exists()) {
releaseValuesFile.inputStream().use { stream ->
releaseValues.load(stream)
}
}
在现有 android 块内添加 release 签名配置,并在已有 buildTypes.release 中引用它:
signingConfigs {
create("release") {
keyAlias = releaseValues.getProperty("keyAlias")
keyPassword = releaseValues.getProperty("keyPassword")
storePassword = releaseValues.getProperty("storePassword")
storeFile = releaseValues.getProperty("storeFile")?.let { path ->
rootProject.file(path)
}
}
}
buildTypes {
release {
signingConfig = signingConfigs.getByName("release")
}
}
将这些内容合并到已有配置,保留项目自己的其他设置。若原来 release 使用 signingConfigs.getByName("debug"),应替换该设置。release 必须引用正式配置;缺少私密配置时,应让 release 签名验证失败,而不是默默退回开发签名。debug 构建可以继续使用工具默认的开发签名。
Flutter 官方也采用“本地 Properties 文件 + Gradle signingConfigs”的配置方式。Flutter Android 签名配置
还要防止缺少配置时 Gradle 生成未签名产物。对于没有 flavor 的示例项目,在文件末尾加上 release 构建前的检查:
tasks.configureEach {
if (name == "preReleaseBuild") {
doFirst {
check(releaseValuesFile.isFile) {
"Release signing requires android/key.properties."
}
}
}
}
有 flavor 的项目应对相应的 release variant 设置检查,并实际验证缺少密钥时构建失败。配置存在但路径、密码或别名错误时,后续签名验证仍应报错。
四、构建正式包,并检查实际签名
先执行项目规定的分析、测试和检查脚本。普通 Flutter 项目的 APK 构建命令为:
flutter build apk --release
如果项目接入了 Shorebird,并希望安装包继续支持热更新,应使用 Shorebird 构建基础版本,例如:
shorebird release android --flutter-version 3.47.7 --artifact apk
示例 SDK 版本应替换为项目实际固定且 Shorebird 支持的版本。新的基础版本要设置合适的新版本号和递增的构建号;同一平台、同一版本不要重复注册。Shorebird 注册成功后,还需要自行安装、分发或提交商店。Shorebird 基础包发布
找到 CLI 输出的 APK 路径,再使用 Android SDK Build Tools 中的 apksigner:
apksigner verify --verbose --print-certs /path/to/app-release.apk
检查签名验证通过、证书名称符合预期、证书 SHA-256 指纹与准备使用的 release 证书一致。开发证书通常带有 Android Debug 名称,不能仅因为文件叫 app-release.apk 就认定它使用了正式签名。
只检查 Gradle 文件不足以证明签名成功,必须检查最终 APK。签名后再修改 APK 内容会破坏签名,因此不要手工改包后直接分发。apksigner 验证说明
还应核对版本号、包名、支持的 CPU 架构和包完整性。仅 arm64 的 APK 不代表所有 Android 设备均可安装;正式签名通过也不代表真机启动、数据保留和网络恢复已经验收。
五、从开发签名切换时怎么处理旧安装
一个常见情况是:手机已有开发签名的测试包,新生成的正式包保持同一包名,却使用另一张证书。普通覆盖安装会因签名不兼容失败。增加版本号、换 APK 文件名,都不能绕过签名校验。
在没有配置兼容签名轮换的情况下,应先确认旧数据如何导出、备份和恢复,再安排旧测试包到正式包的迁移。卸载会影响应用私有数据,不应为了完成演示自动卸载或清空用户数据。改包名可以同时安装,但新应用会有自己的数据空间,原来的本地数据不会自动迁移。
正式应用发布后,应稳定保管其签名。Android 有受条件限制的签名轮换和 Google Play 密钥升级机制,但不能把“换个密钥重新打包”当成普通升级流程。签名管理与升级
六、备份、商店与热更新的边界
keystore 已加密码,不意味着密码文件也已经加密。将密钥、密码和恢复说明存入受控的加密备份,至少准备一份独立于当前开发机器的备份,并验证能恢复签名身份。只备份 APK、公开证书或指纹,无法恢复私钥。
直接分发 APK 时,开发者自己持有并使用应用签名密钥。接入 Google Play App Signing 时,还要区分上传密钥与最终应用签名密钥;Google Play 可以用托管的应用签名密钥为用户安装包签名。配置第三方登录等服务时,要核对最终安装包对应的证书,而不是只看上传证书。Google Play App Signing
Android APK 签名和 Shorebird 补丁签名是两个机制。给 APK 配置正式签名,不会自动启用 Shorebird 的补丁私钥签名;接入热更新也不会自动替代 Android 安装包签名。Shorebird 补丁签名
可以记录这些非秘密验收信息:release 构建结果、签名算法、证书指纹、APK 摘要、基础版本、架构以及测试设备结果。公开文章使用占位值,删除真实包名、个人目录、账号、组织 ID 和生产地址,并检查截图是否泄露密钥路径或密码。
文档核对日期:2026-10-14。本文范围是 Android APK 签名;iOS 证书与分发方式需另行配置。
如果您觉得这篇文章有帮助,请点个赞吧~
评论
请登录后发表评论
去登录