expo-build-debug-apk-gh

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Build Debug APK qua GitHub Actions (app Expo/RN)

通过GitHub Actions构建Debug APK(Expo/RN应用)

Áp dụng chung cho mọi project Expo/React Native (không phụ thuộc repo).
适用于所有Expo/React Native项目(与仓库无关)。

1. ⚠️ BẮT BUỘC: debug APK phải nhúng JS bundle (nếu không app chết "Unable to load script")

1. ⚠️ 强制要求:Debug APK必须嵌入JS bundle(否则应用会报错“Unable to load script”)

assembleDebug
mặc định KHÔNG bundle JS vào APK — APK tìm Metro dev server ở
localhost:8081
. Cài lên máy thật không có Metro → app mở lên rồi chết/trắng hình (logcat:
Unable to load script ... Make sure you're running Metro
).
Cách fix: config plugin chèn vào
react {}
block của
android/app/build.gradle
(xem chi tiết skill
expo-apk-standalone
— gồm bảng property theo RN version + bước verify
ReactExtension.kt
):
  • RN ≤ 0.7x:
    bundleInDebug = true
  • RN 0.76+ / Expo SDK 5x:
    debuggableVariants = []
    (bundleInDebug đã bị xóa)
Kiểm tra plugin còn hoạt động trước mỗi lần push build:
bash
cd <project>            # vd: apps/mobile
npx expo prebuild --platform android --no-install
默认的
assembleDebug
不会将JS打包到APK中——APK会在
localhost:8081
查找Metro开发服务器。安装到无Metro的真机上后,应用打开后会崩溃/显示白屏(logcat日志:
Unable to load script ... Make sure you're running Metro
)。
修复方法:通过配置插件将代码添加到
android/app/build.gradle
react {}
代码块中(详情参考技能
expo-apk-standalone
——包含对应RN版本的属性表以及验证
ReactExtension.kt
的步骤):
  • RN ≤ 0.7x:
    bundleInDebug = true
  • RN 0.76+ / Expo SDK 5x:
    debuggableVariants = []
    bundleInDebug
    已被移除)
每次推送构建前检查插件是否正常工作:
bash
cd <project>            # 例如:apps/mobile
npx expo prebuild --platform android --no-install

PHẢI thấy debuggableVariants = [] (hoặc bundleInDebug = true theo RN version):

必须看到debuggableVariants = [](或对应RN版本的bundleInDebug = true):

grep -n -A4 "react {" android/app/build.gradle
grep -n -A4 "react {" android/app/build.gradle

PHẢI thấy applicationId + app_name đúng:

必须确认applicationId和app_name正确:

grep -n "applicationId" android/app/build.gradle grep -n "app_name" android/app/src/main/res/values/strings.xml
undefined
grep -n "applicationId" android/app/build.gradle grep -n "app_name" android/app/src/main/res/values/strings.xml
undefined

1b. ⚠️ BẮT BUỘC: inject env CÔNG KHAI (
EXPO_PUBLIC_*
) vào workflow — thiếu env = APK build OK nhưng app lỗi runtime trên máy thật

1b. ⚠️ 强制要求:将公开环境变量(
EXPO_PUBLIC_*
)注入工作流——缺少环境变量会导致APK构建成功但真机运行时出错

Bug thật đã gặp (Expo + Supabase): app cài lên phone mở được bình thường nhưng không đăng ký/login được, mọi call backend đều fail — vì APK build trên CI chứa placeholder URL (
https://placeholder.supabase.co
) do code có fallback khi
process.env.EXPO_PUBLIC_*
rỗng, mà
.env
bị gitignore nên CI build không có env nào được inject.
曾遇到的真实Bug(Expo + Supabase):应用安装到手机后能正常打开但无法注册/登录,所有后端调用均失败——原因是CI上构建的APK包含占位符URL
https://placeholder.supabase.co
),因为代码中当
process.env.EXPO_PUBLIC_*
为空时会触发降级逻辑,而
.env
被git忽略,导致CI构建时没有注入任何环境变量。

Nguyên nhân gốc

根本原因

  • EXPO_PUBLIC_*
    được inline vào JS bundle lúc build — không có env trong CI = rỗng → rơi vào fallback (placeholder/rỗng) mà không báo lỗi build.
  • APK build thành công + mở được ≠ backend hoạt động. Lỗi chỉ lộ khi cài máy thật và thao tác (signup/login/fetch).
  • EXPO_PUBLIC_*
    会在构建时内联到JS bundle中——CI中无环境变量则为空值→触发降级逻辑(占位符/空值)且不会在构建时报错
  • APK构建成功且能打开≠后端功能正常。只有安装到真机并操作(注册/登录/数据获取)时才会暴露问题。

Cách fix (áp dụng chung mọi project Expo có backend)

修复方法(适用于所有带后端的Expo项目)

Inject các biến công khai vào
env:
của job build trong workflow:
yaml
env:
  EXPO_PUBLIC_SUPABASE_URL: ${{ secrets.EXPO_PUBLIC_SUPABASE_URL || 'https://<project-ref>.supabase.co' }}
  EXPO_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.EXPO_PUBLIC_SUPABASE_ANON_KEY || 'eyJ...' }}
  # ... mọi EXPO_PUBLIC_* khác (AdMob unit IDs, API public, v.v.)
  • EXPO_PUBLIC_*
    là public-by-design
    : chúng nằm trong APK, ai giải nén cũng đọc được — KHÔNG phải secret thật, đặt giá trị fallback trực tiếp là OK. Lớp bảo vệ thật là RLS/server-side, không phải giấu key.
  • secrets.X || 'fallback'
    cho phép user override sau bằng repo secret nếu cần đổi môi trường, không cần sửa workflow.
  • TUYỆT ĐỐI không inject secret thật (service role key, token, DB password) vào env build — chúng sẽ nằm trong APK bị giải nén được.
将所有公开变量注入到工作流的构建任务
env:
中:
yaml
env:
  EXPO_PUBLIC_SUPABASE_URL: ${{ secrets.EXPO_PUBLIC_SUPABASE_URL || 'https://<project-ref>.supabase.co' }}
  EXPO_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.EXPO_PUBLIC_SUPABASE_ANON_KEY || 'eyJ...' }}
  # ...其他所有EXPO_PUBLIC_*变量(如AdMob单元ID、公开API地址等)
  • EXPO_PUBLIC_*
    设计为公开变量
    :它们会被包含在APK中,任何人解压都能读取——并非真正的机密,直接设置降级值是安全的。真正的防护是RLS(行级安全)/服务端验证,而非隐藏密钥。
  • secrets.X || 'fallback'
    允许用户后续通过仓库机密覆盖值以切换环境,无需修改工作流。
  • 绝对不要注入真实机密(如服务角色密钥、令牌、数据库密码)到构建环境变量中——它们会被包含在APK中,可被解压获取。

Verify trước khi push (thêm vào checklist mục 2a)

推送前验证(添加到2a步骤的检查清单)

bash
undefined
bash
undefined

Workflow có đủ EXPO_PUBLIC_* cho app không?

工作流是否包含应用所需的所有EXPO_PUBLIC_*变量?

grep -n "EXPO_PUBLIC_" .github/workflows/<build>.yml
grep -n "EXPO_PUBLIC_" .github/workflows/<build>.yml

Mọi biến app đọc đều có trong workflow env (không chỗ nào rơi vào placeholder)

应用读取的所有变量都已在工作流环境中配置(无触发降级到占位符的情况)

undefined
undefined

Chẩn đoán nhanh khi app cài máy thật mà backend fail (không cần đọc code)

应用安装到真机后后端功能失效的快速诊断(无需查看代码)

bash
undefined
bash
undefined

APK có chứa placeholder/URL sai không?

APK是否包含占位符/错误URL?

strings app-debug.apk | grep -iE "placeholder|localhost|http://" | head
strings app-debug.apk | grep -iE "placeholder|localhost|http://" | head

Đối chiếu với URL backend thật của project

与项目真实后端URL对比

undefined
undefined

2. Quy trình (khi user muốn build APK test)

2. 流程(当用户需要构建测试APK时)

a. Đảm bảo code sạch lỗi trước khi push (theo AGENTS.md)

a. 推送前确保代码无错误(遵循AGENTS.md)

CI chỉ build, không sửa lỗi:
bash
cd <project>
npx tsc --noEmit           # typecheck
npm run lint               # lint
npx jest                   # test
npx expo export --platform android 2>&1 | tail -2   # bundle check
CI仅负责构建,不修复错误:
bash
cd <project>
npx tsc --noEmit           # 类型检查
npm run lint               # 代码检查
npx jest                   # 测试
npx expo export --platform android 2>&1 | tail -2   # 打包检查

b. Commit + push lên branch build

b. 提交并推送到build分支

bash
cd <project-root>
git add -A
git commit -m "message"
git push origin <branch>   # workflow trigger tự động
⚠️ Push KHÔNG lộ token trong remote URL — dùng header hoặc URL tạm (token do user cấp trong từng phiên, không hardcode; nhắc user revoke sau khi xong).
bash
cd <project-root>
git add -A
git commit -m "message"
git push origin <branch>   # 自动触发工作流
⚠️ 推送时不要在远程URL中暴露令牌——使用请求头或临时URL(令牌由用户在每次会话中提供,不要硬编码;提醒用户使用后撤销令牌)。

c. Theo dõi workflow (không cần đợi build xong nếu user không yêu cầu)

c. 监控工作流(如果用户没有要求,无需等待构建完成)

bash
curl -s -H "Authorization: Bearer $GH_TOKEN" \
  "https://api.github.com/repos/<owner>/<repo>/actions/runs?per_page=3"
bash
curl -s -H "Authorization: Bearer $GH_TOKEN" \
  "https://api.github.com/repos/<owner>/<repo>/actions/runs?per_page=3"

d. Tải APK về khi build xong

d. 构建完成后下载APK

bash
undefined
bash
undefined

Lấy artifact download URL:

获取制品下载URL:

curl -s -H "Authorization: Bearer $GH_TOKEN"
"https://api.github.com/repos/<owner>/<repo>/actions/artifacts" | python3 -m json.tool
curl -s -H "Authorization: Bearer $GH_TOKEN"
"https://api.github.com/repos/<owner>/<repo>/actions/artifacts" | python3 -m json.tool

Download + giải nén:

下载并解压:

curl -sL -H "Authorization: Bearer $GH_TOKEN" -o apk.zip "<archive_download_url>" unzip -o -q apk.zip && ls -lh *.apk

APK nằm trong artifact `<tên-artifact>` (`app-debug.apk`), signed bằng debug keystore — cài lên máy test được, không submit store.
curl -sL -H "Authorization: Bearer $GH_TOKEN" -o apk.zip "<archive_download_url>" unzip -o -q apk.zip && ls -lh *.apk

APK位于制品`<artifact-name>`中(`app-debug.apk`),使用Gradle默认的调试密钥库签名——可安装到测试设备上,不可提交到应用商店。

3. Chi tiết workflow (debug APK, no keystore) — áp dụng chung

3. 工作流详情(Debug APK,无需密钥库)——通用方案

  • Build:
    ./gradlew assembleDebug
    (không cần keystore riêng — debug keystore mặc định của Gradle) — kèm bundle JS qua config plugin (mục 1)
  • Upload:
    android/app/build/outputs/apk/debug/app-debug.apk
  • Bước chuẩn: checkout → Node (LTS) → JDK 17 → Android SDK (accept licenses) →
    npm ci
    expo prebuild --platform android --clean --no-install
    (CI=1) →
    gradlew assembleDebug
    → upload
  • BẮT BUỘC có
    env:
    với đủ
    EXPO_PUBLIC_*
    mà app đọc (mục 1b) — không inject thì app mở được nhưng signup/login/fetch đều fail (placeholder)
  • KHÔNG dùng: EAS token, keystore production, signing release
  • Native config chỉ lộ lỗi khi build thật: nếu build fail, đọc log step "Build debug APK" — các lỗi thường gặp: pin transitive dependency theo Kotlin metadata (xem skill
    gh-actions-expo-apk-build
    ), property
    react {}
    sai theo RN version (mục 1).
  • 构建命令:
    ./gradlew assembleDebug
    (无需单独密钥库——使用Gradle默认的调试密钥库)——通过配置插件嵌入JS bundle(第1部分)
  • 上传路径:
    android/app/build/outputs/apk/debug/app-debug.apk
  • 标准步骤: 检出代码 → Node(LTS版本)→ JDK 17 → Android SDK(接受许可证)→
    npm ci
    expo prebuild --platform android --clean --no-install
    (CI=1)→
    gradlew assembleDebug
    → 上传制品
  • 必须包含
    env:
    :配置应用读取的所有
    EXPO_PUBLIC_*
    变量(第1b部分)——不注入的话,应用能打开但注册/登录/数据获取均会失败(使用占位符)
  • 禁止使用: EAS token、生产密钥库、发布签名
  • 原生配置仅在实际构建时暴露错误:如果构建失败,查看“Build debug APK”步骤的日志——常见错误:根据Kotlin元数据固定传递依赖(参考技能
    gh-actions-expo-apk-build
    )、对应RN版本的
    react {}
    属性配置错误(第1部分)。

Tại sao build APK mới tìm chính xác lỗi

为什么构建APK才能准确定位Bug

  • npx expo export
    chỉ bundle JS — không bắt lỗi native (AdMob, WebView, Gradle, manifest).
  • Debug APK cài lên máy thật mới lộ: lỗi native module, manifest, ads hiển thị, navigation bar che UI.
  • Luôn khuyên user: "build APK rồi cài lên máy để test lỗi thật" khi nghi ngờ lỗi native/runtime.
  • npx expo export
    仅打包JS——无法捕获原生错误(如AdMob、WebView、Gradle、清单文件问题)。
  • Debug APK安装到真机后才会暴露:原生模块错误、清单文件问题、广告显示异常、导航栏遮挡UI等。
  • 始终建议用户:当怀疑存在原生/运行时Bug时,“构建APK并安装到真机上测试真实Bug”。

Đổi sang Release APK (khi cần)

切换到Release APK(如需)

Chỉnh workflow:
assembleDebug
assembleRelease
+ path
apk/release/app-release.apk
. Release APK từ GH Actions vẫn debug-signed (không submit store). Muốn AAB/Play Store → dùng EAS production (xem skill
gh-actions-expo-apk-build
).
修改工作流:将
assembleDebug
改为
assembleRelease
,路径改为
apk/release/app-release.apk
。通过GitHub Actions构建的Release APK仍使用调试签名(不可提交到应用商店)。如果需要AAB/提交到Play Store,请使用EAS生产构建(参考技能
gh-actions-expo-apk-build
)。