GitHub Actions 安卓 APK CI 签名与 Release 全流程实战总结
记录一次 Android 项目使用 GitHub Actions 自动构建、签名、发布 APK 并创建 Release 的完整实践过程。本文重点不是"能跑",而是:为什么会失败、如何定位、以及最终稳定方案,方便后期自己回看,也给后来人少踩坑。
一、背景与目标
背景
-
Android 项目(Kotlin)
-
使用 GitHub Actions 进行 CI
-
每次 push
master:- 自动修改版本号
- 构建 Release APK
- 使用 正式 keystore 签名
- 上传服务器
- 创建 GitHub Release
核心目标
所有 Release APK 必须使用同一份签名证书
否则会出现:
- 新版本无法覆盖安装
- 系统提示"签名不一致"
二、整体流程设计(最终形态)
整体 CI 流程如下:
push master
↓
GitHub Actions
↓
恢复 keystore(Base64 → jks)
↓
注入签名参数(环境变量)
↓
Gradle assembleRelease
↓
校验 APK 签名信息
↓
创建 GitHub Release
三、核心问题与踩坑记录
❌ 问题 1:base64: invalid input
现象:
text
base64: invalid input
Error: Process completed with exit code 1
根因:
- keystore 在本地用 base64 编码后
- GitHub Actions 中使用了不稳定的参数:
bash
base64 -di
不同 Linux 发行版对 -d -i 行为并不完全一致。
正确方式(稳定):
bash
base64 --decode
❌ 问题 2:Gradle 提示 Keystore file not found
报错信息:
text
Execution failed for task ':app:validateSigningRelease'
Keystore file 'xxx-release.jks' not found
常见误区:
- keystore 已生成
- 但 Gradle 使用的路径 ≠ 实际生成路径
例如:
text
生成位置: app/reward-release.jks
Gradle 查找: reward-release.jks
👉 路径必须 100% 对齐,否则直接失败。
❌ 问题 3:gradle.properties 写了,但 Gradle 根本没用
一开始采用方案:
bash
echo "RELEASE_KEYSTORE_PASSWORD=xxx" >> gradle.properties
但 build.gradle.kts 实际使用的是:
kotlin
System.getenv("RELEASE_KEYSTORE_PASSWORD")
👉 结论:
写进
gradle.properties≠ Gradle 一定会读
这也是 CI 中最隐蔽、最容易误判的问题之一。
四、最终采用的稳定方案(强烈推荐)
核心原则
- keystore 文件:CI 运行时恢复
- 敏感信息:只走 GitHub Secrets
- Gradle :只从
System.getenv()读取 - 不污染仓库、不写死密码
五、GitHub Actions 关键配置(最终版)
1️⃣ 恢复 keystore
yaml
- name: Restore release keystore
run: |
echo "${{ secrets.RELEASE_KEYSTORE_BASE64 }}" | base64 --decode > app/reward-release.jks
2️⃣ 构建 Release APK(注入环境变量)
yaml
- name: Build Release APK
env:
RELEASE_KEYSTORE_PASSWORD: ${{ secrets.RELEASE_KEYSTORE_PASSWORD }}
RELEASE_KEY_ALIAS: ${{ secrets.RELEASE_KEY_ALIAS }}
RELEASE_KEY_PASSWORD: ${{ secrets.RELEASE_KEY_PASSWORD }}
run: ./gradlew assembleRelease --stacktrace --no-daemon
3️⃣ (可选)打印 APK 签名信息
yaml
- name: Print APK signing info
run: |
$ANDROID_HOME/build-tools/34.0.0/apksigner verify --print-certs app/build/outputs/apk/release/*.apk
用于确认:
- SHA1 / SHA256
- 是否为同一签名证书
六、Gradle 签名配置(Kotlin DSL)
kotlin
android {
signingConfigs {
create("release") {
storeFile = file("$rootDir/app/reward-release.jks")
storePassword = System.getenv("RELEASE_KEYSTORE_PASSWORD")
keyAlias = System.getenv("RELEASE_KEY_ALIAS")
keyPassword = System.getenv("RELEASE_KEY_PASSWORD")
}
}
buildTypes {
getByName("release") {
signingConfig = signingConfigs.getByName("release")
isMinifyEnabled = false
}
}
}
七、结果验证
✅ 构建结果
- CI 稳定通过
- 每次 Release APK 均使用同一证书
- 新版本可直接覆盖安装旧版本
✅ GitHub Release
- 每个版本一个 tag
- Release 附件即签名 APK
八、经验总结(强烈建议记住)
CI 签名问题 99% 排查顺序
只看三件事:
1️⃣ keystore 是否真的恢复成功
2️⃣ Gradle 是否真的使用了它
3️⃣ 路径是否完全一致
最佳实践清单
- ❌ 不要把 keystore 提交到仓库
- ❌ 不要把密码写死在 gradle 文件
- ✅ GitHub Secrets + Base64
- ✅
System.getenv() - ✅ CI 中打印签名信息做校验
九、结语
这套方案不是"理论最优",而是:
在真实 CI 环境中被问题反复验证后,最稳定、最不容易踩坑的一种方式。
后续如果要支持 AAB / Play Store / 多渠道包,也可以在此基础上无痛扩展。