Expo 项目在 GitHub Actions 上打包 Android Release

Jun 02, 2026 · 5076 字

最近把 Expo 项目的 Android Release 打包流程迁移到了 GitHub Actions 上。原本以为只是写个 workflow 的事,结果一路踩了不少坑。这里把整个过程里遇到的问题和解决方案整理出来,供有类似需求的同学参考。

背景与目标

项目是一个使用 Expo 管理的 React Native 应用,采用 monorepo 结构(pnpm workspace)。希望实现:

  • 推送 tag 触发 release 时,自动构建并上传 APK
  • 支持手动触发 workflow_dispatch,方便日常测试
  • 构建产物既能通过 GitHub Release 下载,也能在 Actions 页面直接拿 Artifacts

Node.js 版本警告

一开始用的是 actions/setup-node@v3,没多久 GitHub Actions 开始发通知:Node 20 即将被弃用,建议迁移到 Node 22 或 24

当时第一反应是去改 setup-nodenode-version

- name: Setup Node
  uses: actions/setup-node@v6
  with:
    node-version: "20"

但这里有个容易混淆的点:actions/setup-node 里的 node-version 只影响 workflow 里后续执行的 node / pnpm 命令(比如 pnpm install、Expo CLI 等),不影响 action 自身的运行时

真正导致 warning 的原因是:android-actions/setup-android@v3 这个 action 自身内部还基于 Node 20 运行。这是 action 作者发布时绑定的运行时,不会因为你在 setup-node 里改成 22 或 24 就自动变掉。

解决方案有两层:

  1. 短期兼容:添加环境变量 FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true,强制 GitHub 上的 JavaScript actions 自身用 Node 24 跑
  2. 长期修复:升级 action 到支持 Node 24 的版本

android-actions/setup-android 版本升级

顺着上面的方向查下去,发现 android-actions/setup-android 其实已经有 v4.0.1 了,而我一直停留在 v3。

升级很简单,直接改版本号:

- name: Setup Android SDK
  uses: android-actions/setup-android@v4

升级到 v4 后,Node 版本的 warning 就彻底消失了,FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true 这个兼容开关也可以去掉了。

APK 架构与体积优化

第一次构建出来的 APK 只有 71MB,感觉还不错。但仔细看配置,发现 gradle.properties 里写的是:

reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64

这意味着构建时会把 armeabi-v7a、arm64-v8a、x86、x86_64 全部打包进去。其中 x86 / x86_64 主要是给模拟器用的,真机发行包其实不需要它们。

如果你的目标是「发给真机安装,尽量小」,通常建议改成:

reactNativeArchitectures=arm64-v8a

如果还想兼容少量老 32 位 Android 真机,可以用:

reactNativeArchitectures=armeabi-v7a,arm64-v8a

改完之后 APK 体积会明显下降,因为不再浪费空间在模拟器 ABI 上。

另外,还可以在 gradle.properties 里开启混淆和资源收缩,进一步减小体积:

android.enableMinifyInReleaseBuilds=true
android.enableShrinkResourcesInReleaseBuilds=true

以及,如果项目里没用到 GIF 或 WebP,可以把 app.json 里对应的解码支持关掉,也能省一点点体积:

{
  "expo": {
    "gif": { "enabled": false },
    "webp": { "enabled": false }
  }
}

GitHub Release 权限问题

在配置 softprops/action-gh-release@v2 上传 APK 到 Release 时,遇到了这样一个错误:

⚠️ Unexpected error fetching GitHub release for tag refs/tags/v0.1.0-alpha:
HttpError: Resource not accessible by integration

原因是:action-gh-release 需要 contents: write 权限来更新 Release 并上传附件,但 workflow 默认拿到的 GITHUB_TOKEN 权限是只读的。

解决方法是在 workflow 文件顶部添加权限声明:

permissions:
  contents: write

加完之后,Release 的更新和附件上传就能正常进行了。

非 Release 触发时的产物获取

除了通过 Release 触发构建,日常测试中更常用的是手动触发 workflow_dispatch。这种情况下,构建产物不会附加到 GitHub Release,但可以通过 Artifacts 下载。

在 workflow 里加上这一步:

- name: Upload APK artifact
  uses: actions/upload-artifact@v7
  with:
    name: app-release-apk
    path: apps/mobile/android/app/build/outputs/apk/release/app-release.apk
    if-no-files-found: error

构建成功后,在 GitHub 仓库页面 → Actions → 点进对应的 workflow run → 页面底部或右侧 Artifacts 里就能看到并下载 app-release-apk

最终的 Workflow 文件

经过上面这些调整,最终的 android-release-build.yml 是这样的:

name: android-release-build

on:
  workflow_dispatch:
  release:
    types: [published]

permissions:
  contents: write

jobs:
  build-release-apk:
    runs-on: ubuntu-latest
    env:
      EXPO_NO_METRO_WORKSPACE_ROOT: "1"
    defaults:
      run:
        working-directory: apps/mobile/android

    steps:
      - name: Checkout
        uses: actions/checkout@v6

      - name: Setup Java
        uses: actions/setup-java@v5
        with:
          distribution: temurin
          java-version: "17"
          cache: gradle

      - name: Setup Android SDK
        uses: android-actions/setup-android@v4

      - name: Setup pnpm
        uses: pnpm/action-setup@v6
        with:
          version: 10.17.1

      - name: Setup Node
        uses: actions/setup-node@v6
        with:
          node-version: "20"
          cache: "pnpm"
          cache-dependency-path: pnpm-lock.yaml

      - name: Install JS dependencies
        run: pnpm install --frozen-lockfile
        working-directory: ${{ github.workspace }}

      - name: Grant Gradle wrapper permission
        run: chmod +x gradlew

      - name: Build release APK
        run: EXPO_NO_METRO_WORKSPACE_ROOT=1 ./gradlew assembleRelease --no-daemon

      - name: Upload APK artifact
        uses: actions/upload-artifact@v7
        with:
          name: app-release-apk
          path: apps/mobile/android/app/build/outputs/apk/release/app-release.apk
          if-no-files-found: error

      - name: Upload APK to GitHub Release
        if: github.event_name == 'release'
        uses: softprops/action-gh-release@v2
        with:
          files: apps/mobile/android/app/build/outputs/apk/release/app-release.apk

几个值得注意的点:

  • EXPO_NO_METRO_WORKSPACE_ROOT=1 是 Expo 在 monorepo 环境下需要的变量,避免 Metro 找不到正确的根目录
  • working-directory 设为 apps/mobile/android,因为 Gradle 构建是在 Android 原生目录下执行的
  • cache-dependency-path 指向根目录的 pnpm-lock.yaml,确保 pnpm 的缓存能正确命中
  • if-no-files-found: error 保证如果 APK 没构建出来,workflow 会直接失败,而不是静默跳过

粤ICP备2025414119号 粤公网安备44030002006951号

© 2026 Saurlax · Powered by Astro