Android 自動化上架:用 fastlane 把 AAB 自動上傳到 Google Play
這篇文章要解決一個很實際的問題:每次要發版,手動構建、上傳 AAB、填測試記錄,既重複又容易出錯。用 fastlane 把它自動化之後,本地一行指令、或是在 CI 推版自動觸發,就能把新版本送到 Google Play 的測試軌或正式版。
適用場景與核心目標
- 適用:原生 Android、Flutter(
build/app/outputs/bundle/release/...)、React Native 等任何能產出 AAB 的專案。 - 核心目標:用 fastlane 把「構建 Release AAB → 上傳到 Google Play」這條路徑自動化,涵蓋本地執行與 GitLab CI/CD 兩種情境。
本文假設你已經有 Google Play Developer 帳號、在 Play Console 建立好 App,也能手動構出一個 Release AAB。如果 App 還是草稿狀態(商店資料、Data safety 沒填完),即使 automation 配好也可能發不出去,請先確認前置條件。
先看結論
整條流程長這樣:
1 | 1. Google Cloud Console:建 Project → 啟用「Google Play Android Developer API」→ 建 Service Account → 下載 JSON key |
跟 iOS 用 App Store Connect 那套 API Key ID / Issuer ID / API Secret 不同,Google Play 這側只需要一個 Service Account JSON key,fastlane 直接讀整個檔案即可。
開始前確認你已經有:
- Google Play Developer 帳號
- 在 Play Console 建立好 App
- 包名(applicationId / package name)已確定
- 能構出 Release AAB
- 環境裝好 Ruby 與 fastlane
前置條件:先確認能手動構出 Release AAB
自動化之前,請先確保你能手動產出可用的 AAB。原生專案在專案根目錄執行:
1 | ./gradlew bundleRelease |
成功後會產生(預設路徑):
1 | app/build/outputs/bundle/release/app-release.aab |
Flutter 則是:
1 | build/app/outputs/bundle/release/app-release.aab |
如果這一步就有問題(例如簽名、編譯錯誤),先把它修好再談自動化。
簽名:Release AAB 怎麼來
Release AAB 必須用你的私鑰簽過名。你需要一個 keystore 檔案(.jks)。如果還沒有,可以用 keytool 產生一支:
1 | keytool -genkeypair -v \ |
接下來讓 Gradle 在構建 Release 時用它簽名。為了讓同一套設定能在本地與 CI 通用,建議用環境變數來讀簽名資訊,而不是硬編碼密碼:
1 | // app/build.gradle |
這樣本地只要準備好 release.jks 並設定環境變數就能簽;CI 則在執行時再注入這些變數,密碼永遠不進 Git。
第一步:在 Google Cloud 建立專案與服務帳號
fastlane 要用 Service Account JSON key 來授權,這東西放在 Google Cloud Console:
- 開 Google Cloud Console,在頂部 Project selector 選 New project,命名例如
google-play-fastlane。 - 這個 Cloud Project 只用來承載 API 與服務帳號,不是你的 App。
啟用 Google Play Android Developer API
在 Cloud Console 搜尋 Google Play Android Developer API,進入頁面後點 Enable。
注意不要啟錯 API,你要的是 Google Play Android Developer API,不是 Android Management API、Google Maps API 或 Firebase API。沒啟用這個,後面就算 JSON 建好了 fastlane 也連不上。
建立 Service Account 並下載 JSON
進入 IAM & Admin → Service Accounts → Create service account:
1 | Service account name: fastlane-supply |
建立後會得到一個帳號郵箱,格式類似:
1 | fastlane-supply@your-project-id.iam.gserviceaccount.com |
把它記下來,稍後要在 Play Console 邀請它。接著下載金鑰:
- 點進剛建立的 Service Account。
- Keys → Add key → Create new key → Key type: JSON → Create。
- 瀏覽器會下載一個
your-project-id-xxxxxx.json。
這個 JSON 裡已經包含 client_email、private_key、project_id 等認證資訊,fastlane 直接讀整個檔案。不要把裡面的 private_key 單獨拆出來,也不用去找額外的 api key。
第二步:在 Play Console 授權服務帳號
只在 Google Cloud 建好服務帳號還不夠,Play Console 必須顯式授權它,否則會報權限錯誤。
- 開 Google Play Console,選目標 App。
- Users and permissions → Invite new users,填入剛才的服務帳號郵箱。
- 按用途分配最小權限,常見幾種:
1 | - 只上傳到測試軌(internal / closed / open): |
如果只是用 fastlane 上傳 AAB,不需要給 Edit store listing,更別開內購、訂單管理的權限。權限給太少會報這些錯:
1 | The caller does not have permission |
第三步:本地安裝與初始化 fastlane
如果還沒裝 fastlane:
1 | gem install fastlane |
團隊協建建議用 Bundler 固定版本。在專案根目錄:
1 | bundle init |
然後在 Gemfile 加入:
1 | source "https://rubygems.org" |
1 | bundle install |
接著進入 Android 專案根目錄初始化:
1 | cd your_android_project |
它會偵測你的專案並產生 fastlane/ 資料夾,裡面有 Appfile 與 Fastfile。
第四步:設定 Appfile 與 Fastfile
Appfile 告訴 fastlane 包名與 JSON key 位置:
1 | # fastlane/Appfile |
建議把 JSON key 放在專案外,例如 ~/.keys/play-store-credentials.json。如果一時必須放進專案,務必加進 .gitignore:
1 | fastlane/*.json |
Fastfile 定義發布 lane。下面是一個上傳到內部測試軌的範例:
1 | # fastlane/Fastfile |
因為簽名設定在 build.gradle 的 signingConfigs.release,gradle bundle 構 Release 時就會自動簽名。如果已在 Appfile 寫入 json_key_file 與 package_name,這裡就不用重複寫。
Flutter 專案的 AAB 路徑不同(build/app/outputs/bundle/release/...),請在 lane 裡用 aab: 明確指定路徑。
執行(用 Bundler 時建議都加 bundle exec):
1 | bundle exec fastlane android internal |
驗證 JSON key 是否可用
上傳前可以先驗證格式與基礎認證沒問題:
1 | fastlane run validate_play_store_json_key json_key:/Users/yourname/.keys/play-store-credentials.json |
常用軌道與發布狀態
Google Play 常見軌道:
1 | internal 內部測試 |
release_status 常見值:
1 | draft 上傳後不會推給任何人,自己去 Play Console 檢查後再提交 |
開發階段建議先用 draft,確認流程穩定後再考慮自動發布。正式環境剛接入 fastlane 時,completed 要謹慎使用。
versionCode 遞增:CI 必做的環節
Google Play 要求每個版本號(versionCode)必須唯一且遞增。手機時代你可能每次手動 +1,自動化後應該讓 CI 自動處理,否則會遇到 Version code has already been used。
最簡單的做法是在 workflow 裡讀取目前的 versionCode、加一後寫回:
1 | CURRENT=$(grep -m1 'versionCode' app/build.gradle | grep -oE '[0-9]+' | head -1) |
這個寫法假設
versionCode定義在app/build.gradle。如果你的版本號是用其他方式管理(例如 Flutter 在pubspec.yaml,或用了 flavor),請改用對應的遞增方式;在 GitLab 中直接用上面那段 script 遞增即可。
安全:金鑰與 keystore 不要進 Git
這兩樣東西一旦提交到公開倉庫就等於暴露,務必小心:
- JSON key:本地放
~/.keys/;CI 裡整份內容存成倉庫 Secret,執行時再寫成檔案。 - keystore 與密碼:keystore 轉 base64 存 Secret,簽名密碼也各存一個 Secret。
.gitignore 至少包含:
1 | *.jks |
CI/CD:用 GitLab CI/CD 自動發布
下面是一個完整範例:推 tag(例如 v1.2.0)時自動構建並上傳到正式版。把這個 .gitlab-ci.yml 放到專案根目錄即可。
GitLab 的機密資料放在 Settings → CI/CD → Variables,不是 GitHub 那套 Secrets。新增以下變數(建議勾選 Masked;keystore 與 JSON key 再勾選 Protected):
1 | GOOGLE_PLAY_SERVICE_ACCOUNT_JSON # JSON key 整份內容 |
Gemfile 與 Gemfile.lock 要提交到倉庫(fastlane 依賴):
1 | # Gemfile |
CI job(.gitlab-ci.yml):
1 | stages: |
幾個重點:
rules: if: '$CI_COMMIT_TAG'表示只有推 tag 才觸發,等同 GitHub Actions 的on: tags。要更精控(例如只在 main 分支發正式版、其他軌道另開 lane)可改用when: $CI_COMMIT_BRANCH等條件。- GitLab 變數用
$VAR引用(跟 shell 一樣),不需要${{ secrets.X }}這種語法。 - keystore 解碼後放在
app/release.jks,對應前面signingConfigs.release的KEYSTORE_PATH;存 base64 時請確保是單行,否則base64 -d會失敗。 - 這個 job 假設 runner 已裝好 JDK 17 與 Ruby(自管 runner 最省事)。若想要容器化,可改用
image:指定鏡像並自行安裝工具鏈,或寫一個 Dockerfile。
常見問題除錯
1. The caller does not have permission / Insufficient permissions
多半是 Play Console 沒邀請服務帳號,或沒給對應 App 的發布權限。回到 Users and permissions 檢查。
2. Package not found
檢查 package_name 是否寫錯、App 是否真的建立、服務帳號是否有該 App 的權限。
3. Version code has already been usedversionCode 重複了。CI 環境務必加上自動遞增那一步;本地手動發版時記得先 +1。
4. 上傳成功但 Play Console 看不到正式版
檢查:上傳到哪個 track、release_status 是 draft 還是 completed、Managed publishing 是否開啟、App dashboard 是否還有必填項未完成。
5. JSON key 驗證通過卻還是連不上
確認 Google Play Android Developer API 有啟用,且 Play Console 已邀請該帳號並授權目標 App。
上線前檢查清單
1 | Google Cloud: |
參考資料
- Google Play Developer API Getting Started:https://developers.google.com/android-publisher/getting_started
- fastlane
upload_to_play_store:https://docs.fastlane.tools/actions/upload_to_play_store/ - fastlane
validate_play_store_json_key:https://docs.fastlane.tools/actions/validate_play_store_json_key/ - Android 簽名文件:https://developer.android.com/studio/publish/app-signing
- GitLab CI/CD 文件(YAML、Variables):https://docs.gitlab.com/ee/ci/