你在填什么
在 LingCode 中点击 Magic Deploy 并选择 Google Play 时,最多会看到七个字段。前三个为必填;若 build.gradle 已配置正式发布签名,则后面四个密钥库字段可以不填。
| 字段 | 含义 | 去哪获取 |
|---|---|---|
| Service account JSON | 创建 Service Account 时 Google 生成的私钥文件。 | Play Console → 设置 → API 访问 → 创建 Service Account → 下载 JSON |
| 软件包名 | 应用的 applicationId,例如 com.example.app。 | 必须与 app/build.gradle 中的 applicationId 完全一致。 |
| 轨道(Track) | 构建发布到哪条轨道:internal、alpha、beta 或 production。 | 在 LingCode 内选择。默认为 internal。 |
| Keystore 文件 | 用于签署正式发布 AAB 的 .jks 或 .keystore。 | 用 keytool 生成一次(或项目中已有)。 |
| Keystore 密码 | 密钥库文件本身的密码。 | 运行 keytool -genkeypair 时设置。 |
| Key alias | 密钥库内该条密钥的名称,例如 release。 | 运行 keytool -genkeypair 时设置。 |
| Key 密码 | 该条密钥的密码。 | 运行 keytool -genkeypair 时设置(常与 Keystore 密码相同)。 |
应用内两处快捷方式能省很多时间。 Magic Deploy 会把 Service Account JSON 做一次 预检——若文件损坏或缺少 client_email / private_key,立刻可见,而不必等到 OAuth 阶段才莫名其妙失败。字段旁的 Auto-bump versionCode 会在每次构建前改写 app/build.gradle 里的 versionCode,消除最常见的第二次部署失败(「Version code X has already been used」)。默认关闭——首次部署时勾选即可。
为什么 Google 用 Service Account 而不是 API Key? 普通 API Key 是单一密钥,持有者能做账号允许的一切——泄露只能轮换并祈祷。Google 的 Service Account 是 Google Cloud IAM 里的真实身份:你可授予窄权限(例如「仅向该应用的内测轨道上传构建」),并独立吊销或轮换密钥。LingCode 与 Google 之间运行的 JWT → OAuth 2.0 流程会把 JWT 换成约一小时有效的访问令牌——即便令牌泄露也会很快过期。术语不熟?见 术语表。
开始之前——一次性 Play Console 配置
在 Service Account 能上传任何东西之前,Google Play Console 里必须先有三样东西。省略任一项都会在后面遇到令人困惑的错误。
- Google Play 开发者账号。 在 play.google.com/console/signup 注册。需一次性支付 25 美元。请使用你打算长期持有该应用的 Google 账号——日后转移所有权可行但很麻烦。
- Play Console 中的应用记录。 在 Play Console 点击 创建应用,填写名称、默认语言、应用或游戏、免费或付费等。此处填写的 软件包名 必须与
app/build.gradle中的applicationId完全一致,且之后不可更改。 - 至少手动上传过一次 AAB。 在人工上传过至少一个签名构建之前,Play 不允许仅凭 Service Account 上传。本地构建签名 AAB(
./gradlew bundleRelease),拖入 Play Console → 测试 → 内部测试 → 创建版本,一次即可。不必正式发布——只要上传即解锁 API 路径。
跳过首次手动上传是「一切正常但 Console 里什么都没有」的头号原因。 API 可能照常接受上传并提交编辑,你在 Play Console 仍看不到构建。请先手动上传一次。
Android 工程必须具备这些设置
Play Console 侧配置正确,但若 ./gradlew bundleRelease 产不出 LingCode 期望的 AAB,仍然无法部署。首次部署前请逐项核对。
app/build.gradle 中的 applicationId
这相当于 Android 的 Bundle Identifier,必须与你在 Play Console 输入的软件包名完全一致。应用上架后,该值不可更改——Google 会把不同的 applicationId 视为完全不同的应用。
android {
defaultConfig {
applicationId "com.example.app"
// …
}
}
versionCode 与 versionName
二者都在 defaultConfig 中。versionCode 是 Play 用来排序上传的整数;versionName 是面向用户的版本号字符串。
defaultConfig {
versionCode 42 // must be greater than every previous upload
versionName "1.2.3" // shown to users in the Play Store
}
每次上传都必须增大 versionCode。 重复使用同一 versionCode 是第二次部署 silent 失败的最常见原因——Play 在服务端拒绝并报「Version code X has already been used」。
minSdkVersion 与 compileSdkVersion
Play Console 对 targetSdkVersion 有逐年提高的底线(目前通常为 API 33+)。若项目的 targetSdkVersion 低于当前底线,上传可能被接受但发布无法铺开。当前最低要求见 developer.android.com/google/play/requirements/target-sdk。
bundleRelease Gradle 任务
LingCode 会运行 ./gradlew bundleRelease,并期望产物位于 app/build/outputs/bundle/release/app-release.aab。标准 Android Studio 工程通常开箱即用。若你重命名了 app 模块、使用了 product flavor,或多模块工程中可上传的 AAB 在别处产出,需要自行调整:
- 重命名模块:LingCode 会查找
app/build/outputs/bundle/release/app-release.aab与build/outputs/bundle/release/app-release.aab。把模块改回标准布局,或临时把 AAB 符号链接到期望路径。 - Product flavors:
bundleRelease会为每个 flavor 构建。若你只需要某一个 AAB——关闭 Magic Deploy 自行运行./gradlew bundle<Flavor>Release,或在 Gradle 中设定默认 flavor。
Gradle Wrapper(gradlew)
LingCode 优先使用 ./gradlew,而非系统的 gradle。若工程没有 wrapper,请添加:
gradle wrapper --gradle-version 8.5
(版本号选你项目已知可构建的版本。)使用 wrapper 可避免 PATH 上任意版本的 Gradle,并与 CI / 同事环境一致。
步骤 1 — 创建 Service Account 并下载 JSON 密钥
与 Google Play 通信时,LingCode 会以 Service Account 身份认证。它在 Play Console 内创建——不是在单独的 Google Cloud Console 里从零开始,尽管 Service Account 本身是 Cloud 概念。
- 登录 play.google.com/console。
- 左侧边栏打开 设置 → API 访问。
- 在 Service accounts 下点击 Create new service account。Play Console 会弹出对话框链接到 Google Cloud;按指引创建 Service Account(名称任意——「LingCode Magic Deploy」 即可),回到 Play Console 标签页点击 Done。
- 回到 API 访问 页面,在列表中找到新建的 Service Account,点击 Manage Play Console permissions(或 Grant access)。
- 在 App permissions 标签点击 Add app,选择此前注册的应用。
- 在 Account permissions 标签至少勾选 Release apps to testing tracks 与 Release apps to production。保存。
- 回到 Google Cloud Console,打开该 Service Account 的 Keys 标签,Add key → Create new key → JSON,下载文件并妥善备份。
在 LingCode 中点击 Service account JSON 旁的 Browse…,选择下载的文件。
JSON 密钥文件相当于密码。 不要提交到 Git,不要粘贴到 Slack。若泄露,在 Cloud Console 删除该密钥——Service Account 仍可继续用,只需换新密钥。
步骤 2 — 选择发布轨道
轨道(Track) 下拉框决定 Play 把上传放到哪里。LingCode 默认为 internal——在试用 Magic Deploy 本身时最快、最安全。
internal— 最多 100 名许可名单测试用户。无审核,即时可用。前几次上传建议用它。alpha— 封闭式测试轨道,通常规模更大,仍为许可名单。beta— 开放式测试,持有链接即可加入。production— 在 Play 商店可见。需经 Google 审核(通常数小时至数日)。
之后可随时在 Play Console 内把构建从 internal 提升到更高轨道,无需重新上传。
步骤 3 — 配置正式发布签名
Google Play 只接受已签名的 AAB。有两种常见做法——按你现有流程选择。
方案 A — 在 build.gradle 内签名(若已有密钥库推荐)
在 app/build.gradle 中添加 signingConfigs.release 块:
android {
signingConfigs {
release {
storeFile file("/absolute/path/to/release.keystore")
storePassword "…"
keyAlias "release"
keyPassword "…"
}
}
buildTypes {
release {
signingConfig signingConfigs.release
// …
}
}
}
若采用此方案,请将 LingCode 中四个密钥库字段留空。LingCode 只运行 ./gradlew bundleRelease,由 Gradle 从构建文件读取签名配置。
方案 B — 由 LingCode 在构建时注入密钥库
若不想把密码硬编码进 build.gradle,让构建文件从 Gradle 属性读取:
android {
signingConfigs {
release {
storeFile file(project.findProperty("RELEASE_STORE_FILE") ?: "release.keystore")
storePassword project.findProperty("RELEASE_STORE_PASSWORD") ?: ""
keyAlias project.findProperty("RELEASE_KEY_ALIAS") ?: ""
keyPassword project.findProperty("RELEASE_KEY_PASSWORD") ?: ""
}
}
buildTypes {
release { signingConfig signingConfigs.release }
}
}
然后在 LingCode 中填写全部四个密钥库字段。构建时 LingCode 会以 -PRELEASE_STORE_FILE=…、-PRELEASE_STORE_PASSWORD=…、-PRELEASE_KEY_ALIAS=…、-PRELEASE_KEY_PASSWORD=… 传给 Gradle。除本地 gradle 进程参数外,这些值不会离开你的 Mac。
还没有密钥库?
用 JDK 自带的 keytool 生成:
keytool -genkeypair -v \
-keystore release.keystore \
-alias release \
-keyalg RSA -keysize 2048 \
-validity 10000
把文件保存在有备份的位置。若丢失此密钥库,你将无法再向 Play 更新该应用——Google 用签名证明新版本确实来自你。请至少在两个地方备份。
步骤 4 — 点击 Deploy
Service Account JSON、软件包名与签名就绪后,Deploy to Google Play 按钮会变为可用。随后 LingCode 会:
- 定位 Android 工程(在打开的文件夹根目录查找
build.gradle或build.gradle.kts)。 - 运行
./gradlew bundleRelease(若无 wrapper 则用gradle bundleRelease);若选方案 B,则以-P属性传入签名凭据。 - 读取
app/build/outputs/bundle/release/app-release.aab。 - 用 Service Account 私钥签署 JWT,在 Google OAuth 端点换取作用于
androidpublisher的访问令牌。 - 创建新编辑、上传 AAB,并在所选轨道上提交该编辑。
成功后约一两分钟内(Google 处理时间,非 LingCode)构建会出现在 Play Console → 测试 → 你的轨道 → 版本。随后可铺开、提升到其他轨道或提交审核。
阅读进度日志
Magic Deploy 运行时,右侧面板会流式输出 gradlew 的实时日志以及 LingCode 对 Google 服务器的每次 HTTP 调用。前缀为 [Google Play] 的行是 LingCode 标记;其余为工具原始输出。
一次成功运行大致如下:
[Google Play] Building signed AAB...
> Task :app:bundleRelease
BUILD SUCCESSFUL in 45s
[Google Play] Getting access token...
[Google Play] Creating edit...
[Google Play] Uploading AAB...
[Google Play] Committing edit...
[Google Play] Upload complete. Check Google Play Console for the new build.
失败时请滚动到日志底部。 LingCode 的短报错(如「Build failed. Check errors above.」)只是指针;真正原因通常在原始输出的最后二十行——往往是 FAILURE: 开头的 Gradle 行,或 HTTP 状态码(Google API)。
分阶段排错
失败按阶段划分较清晰,各自对策不同。
构建阶段(gradlew bundleRelease)
| 日志含 | 原因 | 处理 |
|---|---|---|
| No Android project found | LingCode 在打开文件夹根目录未找到 build.gradle 或 build.gradle.kts。 |
打开包含 gradlew 的文件夹,而非其父目录。若在 monorepo 中,可临时直接打开 Android 子目录。 |
| Permission denied: ./gradlew | Wrapper 脚本不可执行。 | 在项目根目录执行 chmod +x gradlew。 |
| Could not find method signingConfig() | Gradle 语法混用——把 Groovy 与 Kotlin DSL 片段混在了一起。 | 按文件扩展名使用匹配语法:build.gradle 为 Groovy,build.gradle.kts 为 Kotlin。 |
| Keystore file '…' not found | Magic Deploy 中的密钥库路径,或 RELEASE_STORE_FILE 指向的路径,在磁盘上解析失败。 |
使用指向 .jks 或 .keystore 的绝对路径。相对路径相对 Gradle 模块目录解析,容易踩坑。 |
| Failed to read key <alias> from store: Keystore was tampered with, or password was incorrect | Keystore 密码或 Key 密码错误。 | 在终端运行 keytool -list -keystore release.keystore,按提示输入 Keystore 密码以验证。 |
| No key with alias "…" found | 密钥库中不存在该 alias。 | keytool -list -keystore release.keystore 会列出全部 alias,将正确名称填入 Key alias 字段。 |
| AAB not found at app/build/outputs/bundle/release/app-release.aab | Gradle 构建成功但未在预期路径产出 AAB——常见原因是 app 模块不叫 app,或 product flavor 改变了输出文件名。 |
将模块改名为 app,或用 Gradle Copy 任务把 AAB 放到该路径。若按 flavor 构建,可暂时手动运行 ./gradlew bundle<Flavor>Release。 |
| Execution failed for task ':app:lintVitalRelease' | Gradle 配置将某条 lint 标为错误,阻塞正式发布构建。 | 修复 lint,或临时加入 android.lintOptions.checkReleaseBuilds false 以解锁(再排期彻底修复)。 |
| Java heap space / OutOfMemoryError | Gradle Daemon 堆内存对你的项目过小。 | 在 gradle.properties 中加入 org.gradle.jvmargs=-Xmx4g。 |
OAuth 阶段(JWT → access token)
| 日志含 | 原因 | 处理 |
|---|---|---|
| OAuth failed: invalid_grant | LingCode 用 Service Account 私钥签署的 JWT 被拒绝。常见为 JSON 损坏或来自错误项目。 | 在 Service Account 的 Keys 标签重新下载 JSON。勿手工编辑——部分编辑器会插入不可见字符。 |
| OAuth failed: invalid_scope | 该 Google Cloud 项目未启用 androidpublisher API。 |
Cloud Console → API 和服务 → 库,搜索并启用 Google Play Android Developer API。 |
| OAuth failed: 401 Unauthorized | Service Account 私钥已被吊销或删除。 | 在 Keys 标签生成新 JSON 密钥,并在 Magic Deploy 中重新附加。 |
Create Edit 阶段
| 日志含 | 原因 | 处理 |
|---|---|---|
| Create edit failed: 404 applicationNotFound | Play Console 中不存在该 packageName——可能是拼写错误,或尚未创建应用。 |
先在 Play Console 创建应用记录(Create app),软件包名与 build.gradle 中 applicationId 完全一致。 |
| Create edit failed: 403 developerDoesNotOwnApplication | Service Account 尚未与此应用在 Play Console 关联。 | Play Console → 设置 → API 访问 → 你的 Service Account → Manage Play Console permissions → App permissions → Add app,添加该应用。 |
| Create edit failed: 403 insufficientScopes | Service Account 的 账号权限过窄。 | 同一页 Account permissions 标签——至少勾选「Release apps to testing tracks」。管理员角色亦可。 |
上传 AAB 阶段
| 日志含 | 原因 | 处理 |
|---|---|---|
| Upload failed: 403 apkNotificationMessageKeyUpgradeVersionConflict / versionCodeTooLow | AAB 中 versionCode 未大于 Play 上已有版本。 |
提高 app/build.gradle 中的 versionCode 并重试——或勾选面板中的 Auto-bump versionCode,由 LingCode 每次部署自动递增。 |
| Upload failed: 400 apkUploadApkNotSigned | AAB 未签名(或使用 debug 密钥签名)。 | 要么在 Magic Deploy 填齐四个密钥库字段,要么在 build.gradle 配置 signingConfigs.release。不要两套同时硬顶——可能冲突。 |
| Upload failed: 400 apkSignedWithDifferentCertificate | 签名证书与 Google 为该应用记录的证书不一致。 | 使用原密钥库签名;若已丢失,通过 Play Console → 设置 → App 签名 → 申请重置上传密钥(由 Google 人工审核)。 |
| Upload failed: 413 Request Entity Too Large | AAB 超过 150 MB 上限。 | 启用 R8/ProGuard 压缩,精简大资源,或使用 Play Asset Delivery。 |
| Upload failed: network error / timed out | 网络慢或不稳定;LingCode 对上传请求有 600 秒超时。 | 重试。若多次上传同一 AAB,Play 去重常会快速返回。 |
Commit Edit 阶段
| 日志含 | 原因 | 处理 |
|---|---|---|
| Commit failed: 400 validationError | 验证阶段 Play 拒绝了编辑——常见为应用记录缺少必填项(内容分级、隐私政策网址、目标受众等)。 | 在 Play Console 打开应用,完成 政策与计划、应用内容 中带红点的部分。上传已成功;修复后可于 Console 手动提交编辑。 |
| Commit failed: 409 editAlreadyCommitted | 上一次尝试已提交该编辑;当前重试无事可做。 | 通常可忽略。查看 Play Console——构建多半已在。 |
上传成功后会发生什么
Magic Deploy 完成后,构建会出现在 Play Console → 你的应用 → 测试 → <你的轨道> → 版本。与 App Store Connect 不同,AAB 通常没有单独的「处理中」状态——约一两分钟内即可用。随后:
- 内部测试 — 许可名单用户可通过加入链接立即安装。无审核。
- 封闭式测试(alpha) — 仍为许可名单,但首次通常需审核(一般 < 24 小时)。
- 开放式测试(beta) — 持有链接即可加入。该轨道首次发布需审核。
- 正式版 — 在 Play 商店可见。由 Google 审核(数小时至数日)。可在同一页分阶段铺开(例如 10% → 50% → 100%)。
可在 Play Console 内将构建从低轨道提升到高轨道而无需重新上传——打开目标轨道的版本页,点击 Create new release → Add from library。
速查:出问题该去哪改
| 现象 | 去哪处理 |
|---|---|
| Deploy 按钮一直灰 | 缺少软件包名或 Service Account JSON。两者齐全后 LingCode 才会启用按钮。 |
| "No Android project found" | 打开含 gradlew / build.gradle 的文件夹,而非父目录。 |
| 构建阶段失败 | 在终端用相同 -P 参数运行 Magic Deploy 日志里打印的 ./gradlew bundleRelease。Gradle 报错与 LingCode 内一致,终端里更易调试。 |
| AAB 已构建但找不到 | 模块不叫 app,或 flavor 产出不同文件名。重命名或符号链接到 app/build/outputs/bundle/release/app-release.aab。 |
| OAuth / 401 | 重新下载 Service Account JSON 并在 Magic Deploy 中附加。 |
| 403 developerDoesNotOwnApplication | Play Console → 设置 → API 访问 → 你的 Service Account → App permissions → 添加该应用。 |
| versionCodeTooLow | 增大 app/build.gradle 中的 versionCode,任意更大整数均可。 |
| 上传成功但 Console 无构建 | 尚未完成首次手动上传。本地构建签名 AAB,拖入 Play Console → 内部测试 → 创建新版本,一次即可。 |
| Commit 报 validationError | 打开 Play Console,处理 政策与计划 / 应用内容 红点,再手动提交编辑(文件已上传)。 |
| 仍卡住 | 把日志中的完整 gradle 命令复制到终端执行;若是 API 错误,用 curl 带上 access token 调同一端点查看完整响应体。 |
常见错误(速查清单)
- "No Android project found." 打开的工程根目录没有
build.gradle或build.gradle.kts。请直接打开 Android 工程(含gradlew的那个目录),而非父目录。 - "AAB not found at
app/build/outputs/bundle/release/app-release.aab." app 模块未产出 AAB——常见于多模块工程bundleRelease产到了别处,或模块不叫app。将模块改名为app,或增加汇总 release bundle 的app子工程。 - "Create edit failed …" 软件包名与
applicationId不一致,或 Service Account 尚未在 Play Console → 设置 → API 访问 → App permissions 关联该应用。 - 上传成功但永远没有构建。 跳过了首次手动上传。请先手工上传任意签名 AAB,再重试。
- 带签名提示的 "Build failed"。 要么方案 A 的
build.gradle指向了本机不存在的密钥库路径,要么方案 B 的-P与build.gradle预期不一致。用 LingCode 相同参数在终端运行./gradlew bundleRelease,Gradle 错误更具体。 - Service Account 权限不足。 向导默认勾选常为只读。请在 Account permissions 标签至少勾选 Release apps to testing tracks。
安全须知
Service Account JSON 与密钥库均为凭据。LingCode 仅在你触发的部署过程中把它们发送到 oauth2.googleapis.com 与 androidpublisher.googleapis.com(即 Google),否则文件只保留在你的 Mac。
若设备丢失或人员离职,请轮换 Service Account 密钥(Cloud Console → IAM → Service accounts → Keys,删除旧钥并生成新钥)。若怀疑密钥库备份泄露,请停止用它发新版——在未通过 Play App Signing 官方流程的情况下,Google 不允许随意更换应用签名密钥。
延伸阅读
本页概念的一手资料,来自 Google:
- LingCode 部署术语表——本页每个术语的定义。
- Google Play Developer API — LingCode 用于创建编辑、上传 AAB、提交版本的 REST API。
- OAuth 2.0 for Server-to-Server Applications — Service Account 使用的 JWT 承载流程。
- Sign your app(Android Developers)——密钥库生成、签名配置与 Play App Signing。
- About Android App Bundles — AAB 的定位及 Play 如何按设备拆分 APK。
- Manage your Play testing tracks — 内部 / alpha / beta / 正式版轨道的实务说明。