gh-actions-expo-apk-build
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGitHub Actions: Build Expo Android APK/AAB
GitHub Actions:构建Expo Android APK/AAB
Build a release Android APK for an Expo (React Native) app on GitHub Actions without paying for or waiting on EAS cloud. Proven on Expo SDK 57 / RN 0.86 with react-native-google-mobile-ads.
无需付费或等待EAS云服务,在GitHub Actions上为Expo(React Native)应用构建正式版Android APK。已在Expo SDK 57 / RN 0.86搭配react-native-google-mobile-ads环境下验证可行。
When to use (vs EAS)
适用场景(对比EAS)
| Situation | Use |
|---|---|
| Test APK on a real device, free, no token, auto on every push | This skill (GH Actions) |
EAS free tier stuck | This skill (GH Actions) |
| Upload to Play Store (AAB, proper signing, Play App Signing) | EAS Build |
| Need iOS build | EAS Build (GH Actions has no macOS here) |
APK built by this workflow is signed with the debug keystore by
default — installable on devices. For a release-signed APK/AAB (needed
for Play Console), pass the release keystore + signing plugin: see the
skill (proven: GH Actions can produce
release-signed artifacts without EAS).
android-release-signing| 场景 | 选择方案 |
|---|---|
| 在真实设备上测试APK,免费、无需令牌、每次推送自动构建 | 本技能(GitHub Actions) |
| EAS免费 tier 排队等待30分钟以上 | 本技能(GitHub Actions) |
| 上传至应用商店(AAB格式、合规签名、Play应用签名) | EAS Build |
| 需要iOS构建 | EAS Build(GitHub Actions无macOS环境) |
本工作流构建的APK默认使用调试密钥库签名——可直接安装到设备上。若需用于Play Console的正式签名APK/AAB,请传入正式密钥库+签名插件:详见技能(已验证:GitHub Actions无需EAS即可生成正式签名制品)。
android-release-signingDecision order
决策优先级
- APK (test) → workflow below, no secrets needed (debug keystore) or with keystore secrets for a release-signed APK.
- AAB (store) → sign with the project release keystore inside the workflow
(decode keystore secret → with signing plugin →
expo prebuild). This works without EAS; see./gradlew bundleReleaseand thereferences/aab-play-store-signing.mdskill. (EAS Build is an alternative if you prefer EAS-managed signing.)android-release-signing - Build failing with a Kotlin/version error → read FIRST. These are the 5-fix failure modes that cost hours.
references/troubleshooting.md
- APK(测试用)→ 使用下方工作流,无需密钥(调试密钥库)或传入密钥库密钥生成正式签名APK。
- AAB(应用商店用)→ 在工作流内使用项目正式密钥库签名(解码密钥库密钥 → 搭配签名插件执行→ 运行
expo prebuild)。无需EAS即可实现;详见./gradlew bundleRelease和references/aab-play-store-signing.md技能。(若偏好EAS托管签名,EAS Build是替代方案。)android-release-signing - 构建因Kotlin/版本错误失败 → 先阅读。这些问题有5种修复模式,曾耗费数小时排查。
references/troubleshooting.md
Procedure (APK, test build)
操作步骤(APK测试构建)
1. Preconditions
1. 前置条件
- Repo is pushed to GitHub (workflow only runs there).
- (if the app uses Firebase/AdMob) is committed.
google-services.json - is NOT committed (gitignored) — CI regenerates it via
android/.expo prebuild - Default branch is (rename
mainfirst:master) or adjust the trigger.git branch -M main
- 代码仓库已推送到GitHub(工作流仅在此运行)。
- (若应用使用Firebase/AdMob)已提交到仓库。
google-services.json - 目录未提交(已加入.gitignore)——CI将通过
android/重新生成该目录。expo prebuild - 默认分支为(若原分支为
main,先执行master重命名)或调整工作流触发分支。git branch -M main
2. Create the workflow
2. 创建工作流
Open and write from the base template. Keep the canonical step order: checkout → Node → JDK 17 → Android SDK → → (with ) → → .
references/workflow-template.md.github/workflows/build-apk.ymlnpm ciexpo prebuild --clean --no-installCI: 1./gradlew assembleReleaseupload-artifact- JDK 17 for Expo SDK 52+ / RN 0.7x+ (21 is not required).
- Always on prebuild so
--cleanmatchesandroid/(plugins, google-services, proguard).app.json - Validate the YAML before pushing (see below).
- Set on the upload step.
if-no-files-found: error
打开,基于基础模板编写。请遵循标准步骤顺序:checkout → Node → JDK 17 → Android SDK → → (搭配环境变量)→ → 。
references/workflow-template.md.github/workflows/build-apk.ymlnpm ciexpo prebuild --clean --no-installCI: 1./gradlew assembleReleaseupload-artifact- Expo SDK 52+ / RN 0.7x+需使用JDK 17(无需JDK 21)。
- 执行prebuild时务必添加参数,确保
--clean目录与android/(插件、google-services、proguard配置)保持一致。app.json - 推送前验证YAML格式(详见下文)。
- 在上传步骤设置。
if-no-files-found: error
3. Push and monitor
3. 推送并监控构建
bash
git add -A && git commit -m 'ci: add APK build workflow' && git push origin main
gh run list --repo <owner>/<repo> --limit 3
gh run watch <run-id> --repo <owner>/<repo> # or poll via gh run view --json status,conclusion
gh run view <run-id> --repo <owner>/<repo> --log-failed # when it fails — read THIS, not the full logFirst build takes ~15–25 min (dependency download). Debug CI with , then for specifics.
--log-failed--log | grep <keyword>bash
git add -A && git commit -m 'ci: add APK build workflow' && git push origin main
gh run list --repo <owner>/<repo> --limit 3
gh run watch <run-id> --repo <owner>/<repo> # 或通过gh run view --json status,conclusion轮询状态
gh run view <run-id> --repo <owner>/<repo> --log-failed # 构建失败时查看此日志,而非完整日志首次构建耗时约15–25分钟(依赖下载)。使用调试CI,或通过定位具体问题。
--log-failed--log | grep <keyword>4. Download the artifact
4. 下载制品
Artifacts expire after 90 days.
bash
gh run download <run-id> --repo <owner>/<repo> --dir apk --pattern '*.zip'制品90天后过期。
bash
gh run download <run-id> --repo <owner>/<repo> --dir apk --pattern '*.zip'or, faster for large artifacts:
大体积制品可使用更快方式:
gh api repos/<owner>/<repo>/actions/artifacts --jq '.artifacts[] | select(.workflow_run.id==<run-id>) | {name, id, size_in_bytes, archive_download_url}'
curl -sL -H "Authorization: Bearer $GH_TOKEN" -o apk/artifact.zip '<archive_download_url>'
unzip -o -q apk/artifact.zip -d apk && ls -lh apk/*.apk
Keep `GH_TOKEN` in an env var / shell export; never print it.gh api repos/<owner>/<repo>/actions/artifacts --jq '.artifacts[] | select(.workflow_run.id==<run-id>) | {name, id, size_in_bytes, archive_download_url}'
curl -sL -H "Authorization: Bearer $GH_TOKEN" -o apk/artifact.zip '<archive_download_url>'
unzip -o -q apk/artifact.zip -d apk && ls -lh apk/*.apk
请将`GH_TOKEN`存入环境变量/shell导出变量;切勿打印该令牌。5. If the build fails with a compile error
5. 若构建因编译错误失败
Open — the Kotlin-metadata and version-pin section covers the exact failure ladder hit by react-native-google-mobile-ads 16.x on RN 0.86.
references/troubleshooting.md打开——其中Kotlin元数据与版本锁定章节涵盖了react-native-google-mobile-ads 16.x在RN 0.86环境下遇到的所有典型失败场景。
references/troubleshooting.md6. AAB / Play Store release
6. AAB / 应用商店发布
Open BEFORE building a store release. Two options:
references/aab-play-store-signing.mdOption A — release keystore in the GH Actions workflow (no EAS, proven).
Add keystore secrets (),
decode them in the workflow, and run with a signing config
plugin so produces a release-signed AAB. Full details +
verify steps (jarsigner, not apksigner, for AAB) in the
skill. Back up the keystore locally — losing it
means losing the ability to update the app.
ANDROID_KEYSTORE_BASE64/PASSWORD/ALIAS/KEY_PASSWORDexpo prebuildbundleReleaseandroid-release-signingOption B — EAS Build. — EAS generates/manages the upload keystore on its servers. Requires the user's (agent can't run it without it). Steps:
npx eas-cli build --platform android --profile productionEXPO_TOKEN- Ensure has a
eas.jsonprofile withproductionandbuildType: "app-bundle".autoIncrement: true - .
npx eas-cli build --platform android --profile production - Back up the keystore: → download
npx eas-cli credentials --platform android(gitignore it).credentials.json - Upload a Play service-account JSON to EAS credentials, then .
npx eas-cli submit --platform android - Never commit keystores.
构建应用商店版本前,请先阅读。有两种方案:
references/aab-play-store-signing.md方案A — 在GitHub Actions工作流中使用正式密钥库(无需EAS,已验证)
添加密钥库密钥(),在工作流中解码,搭配签名配置插件执行,使生成正式签名AAB。完整细节+验证步骤(AAB需使用jarsigner而非apksigner)详见技能。请在本地备份密钥库——丢失密钥库将无法更新应用。
ANDROID_KEYSTORE_BASE64/PASSWORD/ALIAS/KEY_PASSWORDexpo prebuildbundleReleaseandroid-release-signing方案B — EAS Build。执行——EAS将在其服务器上生成/管理上传密钥库。需要用户的(无令牌则无法执行)。步骤如下:
npx eas-cli build --platform android --profile productionEXPO_TOKEN- 确保包含
eas.json配置文件,其中production且buildType: "app-bundle"。autoIncrement: true - 执行。
npx eas-cli build --platform android --profile production - 备份密钥库:执行→ 下载
npx eas-cli credentials --platform android(加入.gitignore)。credentials.json - 将Google Play服务账号JSON上传至EAS凭据,然后执行。
npx eas-cli submit --platform android - 切勿提交密钥库到仓库。
Reference routing
参考文档指引
| Open when... | File |
|---|---|
| Writing or adjusting the workflow YAML (steps, JDK/Node versions, caching, debug/release variants, secrets) | |
Building for the Play Store: AAB, keystore, Play App Signing, | |
Build fails at | |
| Need to pin a transitive Gradle dependency via config plugin | |
| Update/maintain this skill's contract | |
| 适用场景 | 文档 |
|---|---|
| 编写或调整工作流YAML(步骤、JDK/Node版本、缓存、调试/正式变体、密钥) | |
应用商店构建:AAB、密钥库、Play应用签名、 | |
构建在 | |
| 需要通过配置插件锁定Gradle传递依赖 | |
| 更新/维护本技能规范 | |
Security notes
安全注意事项
- Never commit keystores or secrets. Debug keystore builds need no secrets.
- If a token (EXPO_TOKEN, GH_TOKEN/ghp_*) was ever pasted into a chat, tell the user to revoke it after the build finishes.
- Token in push URL leaks via : push with
git remote -vinstead.git -c http.extraheader="AUTHORIZATION: basic $(printf 'x-access-token:%s' "$GH_TOKEN" | base64)" push origin main
- 切勿提交密钥库或密钥到仓库。调试密钥库构建无需密钥。
- 若令牌(EXPO_TOKEN、GH_TOKEN/ghp_*)曾粘贴到聊天窗口,请告知用户构建完成后立即撤销该令牌。
- 推送URL中的令牌会通过泄露:请改用
git remote -v执行推送。git -c http.extraheader="AUTHORIZATION: basic $(printf 'x-access-token:%s' "$GH_TOKEN" | base64)" push origin main
AAB signing essentials
AAB签名要点
- Upload key vs App Signing key: your keystore (project-managed in the workflow, or EAS-managed) signs the AAB; Google's Play App Signing key re-signs user APKs. They must differ, and losing your upload key is recoverable (Google Play Support reset, with a 72-hour validity window) because Google holds the final key.
- Back up the keystore immediately after the first production build (→ download
eas credentials); never commit it.credentials.json - needs a Google Play service-account JSON (Release Manager permission) uploaded to EAS credentials.
eas submit
- 上传密钥 vs 应用签名密钥:你的密钥库(工作流中项目托管或EAS托管)对AAB签名;Google的Play应用签名密钥会重新对用户下载的APK签名。两者必须不同,且丢失上传密钥可恢复(通过Google Play支持重置,有72小时有效期窗口),因为Google持有最终签名密钥。
- 首次正式构建后立即备份密钥库(执行→ 下载
eas credentials);切勿提交到仓库。credentials.json - 需要Google Play服务账号JSON(需拥有Release Manager权限)并上传至EAS凭据。
eas submit
Golden rules (learned the hard way)
黄金准则(从实践中总结)
- Don't bump Kotlin to read newer metadata. Raising can break other libs (e.g. react-native-safe-area-context crashes on Kotlin 2.3) and still not fix the module. Pin the offending native dependency DOWN instead.
kotlinVersion expo-build-propertiesonly writes gradle.properties — it does NOT override the RN version catalog, so it often has zero effect. Verify inkotlinVersionafter prebuild.android/gradle/libs.versions.toml- Verify the real cause before patching. Resolve what version Gradle actually picked and inspect the module's Gradle file / AAR metadata; guessing causes repeated failed runs.
- Config-plugin pins are the durable fix (prebuild regenerates each run); hand-editing
android/is wiped on the next prebuild.android/build.gradle - When a pinned version lacks APIs the module calls, the compiler error names the exact symbol — patch only those call sites via patch-package, with a safe fallback, and verify the patch applies on a pristine copy.
- 不要为读取新版本元数据升级Kotlin版本。提升可能破坏其他库(例如react-native-safe-area-context在Kotlin 2.3环境下崩溃),且仍无法修复问题。应降级锁定出问题的原生依赖版本。
kotlinVersion - 的
expo-build-properties仅写入gradle.properties——它不会覆盖RN版本目录,因此通常无效。请在prebuild后查看kotlinVersion验证配置。android/gradle/libs.versions.toml - 补丁前先确认真实原因。确定Gradle实际选择的版本,检查模块的Gradle文件/AAR元数据;盲目猜测会导致多次构建失败。
- 配置插件锁定是持久化修复方案(每次prebuild都会重新生成目录);手动编辑
android/会在下一次prebuild时被覆盖。android/build.gradle - 当锁定的版本缺少模块调用的API时,编译错误会明确指出缺失的符号——仅通过patch-package修补这些调用位置,并添加安全回退,同时验证补丁在纯净环境下可正常应用。