這篇文章要解決一個很實際的問題:每次要發版,手動構建、上傳 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 2 3 1. Google Cloud Console:建 Project → 啟用「Google Play Android Developer API」→ 建 Service Account → 下載 JSON key 2. Google Play Console:邀請該 Service Account 帳號 → 給它最小必要權限 3. 本地 / CI:用 JSON key + fastlane 構建 AAB 並上傳到指定軌道
跟 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 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 2 3 4 5 6 7 8 keytool -genkeypair -v \ -keystore release.jks \ -alias release-key \ -keyalg RSA \ -keysize 2048 \ -validity 36500 \ -storepass YOUR_STORE_PASSWORD \ -keypass YOUR_KEY_PASSWORD
接下來讓 Gradle 在構建 Release 時用它簽名。為了讓同一套設定能在本地與 CI 通用,建議用環境變數來讀簽名資訊,而不是硬編碼密碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 android { ... signingConfigs { release { storeFile file(System.getenv('KEYSTORE_PATH' ) ?: "release.jks" ) storePassword System.getenv('KEYSTORE_PASSWORD' ) keyAlias System.getenv('KEY_ALIAS' ) keyPassword System.getenv('KEY_PASSWORD' ) } } buildTypes { release { signingConfig signingConfigs.release minifyEnabled false } } }
這樣本地只要準備好 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 2 Service account name: fastlane-supply Description: Used by fastlane to upload Android releases to Google Play
建立後會得到一個帳號郵箱,格式類似:
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 2 3 4 5 6 7 8 9 10 - 只上傳到測試軌(internal / closed / open): View app information Create and edit releases Release apps to testing tracks - 要發正式版:再加 Release apps to production - 要管理商店頁文案、截圖、圖標:再加 Edit store listing
如果只是用 fastlane 上傳 AAB,不需要給 Edit store listing,更別開內購、訂單管理的權限。權限給太少會報這些錯:
1 2 3 The caller does not have permission Insufficient permissions Package not found
第三步:本地安裝與初始化 fastlane 如果還沒裝 fastlane:
團隊協建建議用 Bundler 固定版本。在專案根目錄:
然後在 Gemfile 加入:
1 2 3 source "https://rubygems.org" gem "fastlane"
接著進入 Android 專案根目錄初始化:
1 2 cd your_android_projectfastlane init
它會偵測你的專案並產生 fastlane/ 資料夾,裡面有 Appfile 與 Fastfile。
第四步:設定 Appfile 與 Fastfile Appfile 告訴 fastlane 包名與 JSON key 位置:
1 2 3 package_name("com.yourcompany.yourapp" ) json_key_file("/Users/yourname/.keys/play-store-credentials.json" )
建議把 JSON key 放在專案外,例如 ~/.keys/play-store-credentials.json。如果一時必須放進專案,務必加進 .gitignore:
Fastfile 定義發布 lane。下面是一個上傳到內部測試軌的範例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 default_platform(:android ) platform :android do desc "Build signed release AAB and upload to internal testing" lane :internal do gradle( task: "bundle" , build_type: "Release" ) upload_to_play_store( track: "internal" , aab: "app/build/outputs/bundle/release/app-release.aab" , release_status: "draft" ) end desc "Build signed release AAB and upload to production" lane :production do gradle(task: "bundle" , build_type: "Release" ) upload_to_play_store( track: "production" , release_status: "inProgress" ) end end
因為簽名設定在 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 2 3 4 internal 內部測試 alpha 封閉測試(舊寫法) beta 開放測試(舊寫法) production 正式版
release_status 常見值:
1 2 3 4 draft 上傳後不會推給任何人,自己去 Play Console 檢查後再提交 inProgress 進入該軌道的審核/發布流程 completed 直接完成發布 halted 暫停
開發階段建議先用 draft,確認流程穩定後再考慮自動發布。正式環境剛接入 fastlane 時,completed 要謹慎使用。
versionCode 遞增:CI 必做的環節 Google Play 要求每個版本號(versionCode)必須唯一且遞增。手機時代你可能每次手動 +1,自動化後應該讓 CI 自動處理,否則會遇到 Version code has already been used。
最簡單的做法是在 workflow 裡讀取目前的 versionCode、加一後寫回:
1 2 3 4 CURRENT=$(grep -m1 'versionCode' app/build.gradle | grep -oE '[0-9]+' | head -1) NEW=$((CURRENT + 1 )) sed -i "s/versionCode ${CURRENT} /versionCode ${NEW} /" app/build.gradle echo "新版本號:$NEW "
這個寫法假設 versionCode 定義在 app/build.gradle。如果你的版本號是用其他方式管理(例如 Flutter 在 pubspec.yaml,或用了 flavor),請改用對應的遞增方式;在 GitLab 中直接用上面那段 script 遞增即可。
安全:金鑰與 keystore 不要進 Git 這兩樣東西一旦提交到公開倉庫就等於暴露,務必小心:
JSON key :本地放 ~/.keys/;CI 裡整份內容存成倉庫 Secret,執行時再寫成檔案。
keystore 與密碼 :keystore 轉 base64 存 Secret,簽名密碼也各存一個 Secret。
.gitignore 至少包含:
1 2 3 *.jks fastlane/*.json local.properties
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 2 3 4 5 GOOGLE_PLAY_SERVICE_ACCOUNT_JSON # JSON key 整份內容 ANDROID_KEYSTORE_B64 # keystore 的 base64(請存成單行,不要換行) ANDROID_KEYSTORE_PASSWORD # keystore 密碼 ANDROID_KEY_ALIAS # 鑰別名 ANDROID_KEY_PASSWORD # 私鑰密碼
Gemfile 與 Gemfile.lock 要提交到倉庫(fastlane 依賴):
1 2 3 source "https://rubygems.org" gem "fastlane"
CI job(.gitlab-ci.yml):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 stages: - deploy deploy_play: stage: deploy tags: - android-runner rules: - if: '$CI_COMMIT_TAG' script: - mkdir -p ~/.keys - echo "$GOOGLE_PLAY_SERVICE_ACCOUNT_JSON" > ~/.keys/play-store-credentials.json - mkdir -p app - echo "$ANDROID_KEYSTORE_B64" | base64 -d > app/release.jks - export KEYSTORE_PATH=app/release.jks - export KEYSTORE_PASSWORD="$ANDROID_KEYSTORE_PASSWORD" - export KEY_ALIAS="$ANDROID_KEY_ALIAS" - export KEY_PASSWORD="$ANDROID_KEY_PASSWORD" - CURRENT=$(grep -m1 'versionCode' app/build.gradle | grep -oE '[0-9]+' | head -1 ) - NEW=$((CURRENT + 1 )) - sed -i "s/versionCode ${CURRENT}/versionCode ${NEW}/" app/build.gradle - bundle install - bundle exec fastlane android production
幾個重點:
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 used versionCode 重複了。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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Google Cloud: [ ] 建立 Cloud Project [ ] 啟用「Google Play Android Developer API」 [ ] 建立 Service Account [ ] 下載 JSON key(放到 ~/.keys/,不要進 Git) Play Console: [ ] 邀請服務帳號郵箱 [ ] 給目標 App 最小必要權限 本地: [ ] 能手構出簽名過的 Release AAB [ ] signingConfig.release 可用環境變數讀到簽名 [ ] Appfile 設定 package_name 與 json_key_file [ ] Fastfile 定義 internal / production lane [ ] validate_play_store_json_key 通過 GitLab CI/CD: [ ] 在 Settings → CI/CD → Variables 新增所有變數 [ ] Gemfile 與 Gemfile.lock 已提交 [ ] keystore 解碼路徑與 KEYSTORE_PATH 一致 [ ] versionCode 自動遞增 [ ] 推 tag 能觸發並成功上傳
參考資料