这篇能做出什么
走完这篇教程,你会拿到一条完整链路:把用 AI 辅助写出来的小应用(Flutter、React Native、Expo、Capacitor、原生 Kotlin 都可以)打包成 Google Play 认可的 AAB 文件,在 Play Console 里建一个只有自己和家人能看到的应用条目,家人用手机点一个链接就装上了,以后改了代码再传一次包,他们那边自动更新。
具体成果:
- 一个带 release 签名的
.aab文件 - 一个 Play Console 应用条目,处于内部测试或封闭测试状态,在商店里搜不到
- 一份测试人员名单,家人用自己的 Google 账号加入后即可安装
- 一套固定动作:改代码 → 升 versionCode → 传新包 → 家人收到更新
全程不需要把应用上架到公开商店,也不会有陌生人下载到你的东西。
---
前置条件清单
动手之前先确认这些东西齐了,缺一样都会在后面卡住:
账号与费用
- 一个 Google 账号,专门用来做开发者账号。不建议拿日常主号,也不建议在同一个网络、同一台设备上频繁注册多个开发者账号
- Play Console 开发者账号。注册需要支付一次性注册费,具体金额以 Play Console 官方注册页面为准。个人账号还需要身份验证(身份证件 + 地址证明),审核要等几天
- 家人的 Google 账号邮箱(每个测试人员必须有一个 Google 账号,QQ 邮箱不行)
技术侧
- 一台能构建 Android 应用的电脑,装好 Android Studio 或命令行 Gradle 环境
- 项目里的
applicationId/package已经定好,并且以后不再改动 - 一个签名密钥文件(keystore),以及它的密码。这个文件丢了会很麻烦,务必单独备份
- 应用的
targetSdk满足 Play 当年的最低要求,具体数值以官方文档为准
物料侧
- 应用图标:512×512 的 PNG
- 特色图片:1024×500 的 PNG/JPG
- 手机截图:至少两张,尺寸要求以 Play Console 页面的实时提示为准
- 一份隐私政策页面。哪怕只给自己用,这一栏也得填。最省事的做法是在 GitHub Pages 放一个静态 HTML,写清楚"本应用不收集任何数据"或者列清楚你实际用到的第三方服务
vibe-coded 项目要额外自查
- 代码里有没有硬编码的 API Key、数据库密码。打包前挪到环境变量或服务端
- 有没有用明文 HTTP 请求。Play 对明文流量有默认限制
- 有没有申请用不到的敏感权限,比如通讯录、后台定位、读取所有已安装应用。多余的权限在审核时会被追问
---
分步骤操作
第 1 步:把 versionCode 和签名配置好
Play 用 versionCode(一个整数)判断新旧,用 versionName(给人看的字符串)做展示。同一个 versionCode 不能传两次,每次更新必须递增。
Flutter 项目,在 pubspec.yaml 里:
```yaml
version: 0.4.0+4
```
加号后面那个 4 就是 versionCode。
Expo 项目,在 app.json 里:
```json
{
"expo": {
"name": "家里的小工具",
"slug": "family-tool",
"android": {
"package": "com.example.familytool",
"versionCode": 4
}
}
}
```
原生或 Capacitor 项目,在 app/build.gradle.kts 里:
```kotlin
android {
defaultConfig {
applicationId = "com.example.familytool"
versionCode = 4
versionName = "0.4.0"
}
}
```
接着生成签名密钥。打开终端:
```bash
keytool -genkey -v \
-keystore ~/keys/familytool-release.jks \
-keyalg RSA -keysize 2048 -validity 10000 \
-alias familytool
```
它会问你姓名、组织、密码。密码记住,密钥文件立刻拷到别的地方存一份。
然后在 app/build.gradle.kts 里引用它,注意不要把密码写死进仓库:
```kotlin
android {
signingConfigs {
create("release") {
storeFile = file(System.getenv("MYAPP_KEYSTORE") ?: "release.jks")
storePassword = System.getenv("MYAPP_STORE_PASSWORD")
keyAlias = System.getenv("MYAPP_KEY_ALIAS")
keyPassword = System.getenv("MYAPP_KEY_PASSWORD")
}
}
buildTypes {
getByName("release") {
signingConfig = signingConfigs.getByName("release")
isMinifyEnabled = true
}
}
}
```
新应用在 Play 上会强制启用 Play 应用签名:你上传的这份叫"上传密钥",Google 那边还会保存一份"应用签名密钥"。所以这份 keystore 是你上传用的凭证,也要保管好。
第 2 步:构建 release AAB
Play 只接受 AAB(Android App Bundle),不接受直接上传的 APK。
Flutter:
```bash
flutter build appbundle --release
```
产物在 build/app/outputs/bundle/release/app-release.aab。
原生 / Capacitor:
```bash
./gradlew bundleRelease
```
产物在 app/build/outputs/bundle/release/app-release.aab。
Expo:
```bash
eas build -p android --profile production
```
构建完会给你一个 AAB 的下载链接。
在 Play Console 里上传之前,先用本地工具确认这个包能被正常解析、签名没问题。Android Studio 的 Build 菜单里有对应的分析入口。
第 3 步:在 Play Console 创建应用
登录 Play Console → 所有应用 → 创建应用。要填:
- 应用名称(会展示给测试人员,写个自己和家人看得懂的名字就行)
- 默认语言
- 应用类型(应用 / 游戏)
- 免费还是付费。注意:一旦选了免费,后续无法改成付费,反过来则可以
创建完成后,进入「测试」→「内部测试」或「封闭测试」。内部测试的审核通常很快,适合自己先跑通;封闭测试更适合长期给家人用,人员上限通常比内部测试宽松,具体人数以官方文档为准。
顺便说一句:Play 还有一条 Managed Google Play「私密应用」的路径,能精确指定组织内的用户安装,但它要求你有 Google Workspace 之类的组织账号,配置门槛比内测轨道高不少。个人给家人用,内测轨道是更实际的选择。
第 4 步:填完「应用内容」和「商店信息」
这一步最枯燥,但不填完就没法发布。
「应用内容」区域里有一串卡片,全部需要变成已完成状态:隐私政策、广告声明、内容分级问卷、目标受众(这里如果选了儿童年龄段,会触发额外的 Families 政策要求,给家人用就直接选成人)、数据安全表单、是否政府应用等等。数据安全表单尤其要认真填——它问你收集哪些数据、是否加密传输、用户能否请求删除。填错是审核被拒的高频原因。
「商店信息」里传图标、特色图片、截图,填简短说明和完整说明。只给自己和家人用时,说明里直接写清楚这是个人用途的小工具。
还要在「国家/地区」里至少勾选一个地区,否则发布按钮是灰的。
第 5 步:上传 AAB 到测试轨道
进入「测试」→「内部测试」→「创建新版本」。按提示完成:
- 如果弹出 Play 应用签名确认,同意即可
- 上传第 2 步生成的
.aab - 填版本说明,比如"修复了记账页面的一个错误"
- 保存 → 检查版本 → 开始发布
首次上传后,应用通常要等一段时间才可用,具体时长视审核队列而定。
第 6 步:把家人加进测试名单
在测试轨道页面找到「测试人员」标签:
1. 创建一个电子邮件列表,把家人的 Google 账号邮箱加进去
2. 保存后页面会给出一个「加入链接」
3. 把链接发给家人
家人那边的操作是:在手机浏览器打开链接 → 用被加入名单的那个 Google 账号登录 → 点「成为测试员」→ 跳转 Play 商店 → 安装。
几个容易踩的点:
- 手机上登录的 Google 账号必须和名单里的一致。很多人手机上有好几个账号,Play 商店默认用了错误的那个,就会出现"找不到此应用"
- 装完之后可以关掉测试页面,应用会一直留着。但如果哪天被移出测试名单,应用会提示需要卸载,所以名单别乱删
- 想让自己也先装一份测试,把自己的邮箱加进去即可
第 7 步:更新版本
以后每次改代码,重复这套动作:
```bash
1. 改 versionCode,比如从 4 改成 5
2. 重新构建
flutter build appbundle --release
3. 回到 Play Console → 内部测试 → 创建新版本 → 上传新的 aab
4. 填写版本说明 → 保存 → 发布
```
发布后,家人的手机在 Play 商店自动更新时就会拉到新版本。想让他们立刻更新,让他们打开 Play 商店 → 管理应用和设备 → 手动检查更新。
---
常见坑与排错
上传时报"版本代码已存在"
versionCode 没递增。回去改 pubspec.yaml、app.json 或 build.gradle.kts 里的数字,重新打包。
上传时报包名不匹配
Play Console 创建应用时填的包名,必须和代码里的 applicationId 完全一致,大小写也要一致。这个值创建后就改不了了,只能新建一个应用条目。
家人打开链接提示"找不到此应用"
依次排查:邮箱是否真的加进名单了(保存后要点「保存」而不是只输入)、手机 Play 商店登录的是不是那个账号、Play 商店缓存是否需要清理(设置 → 应用 → Google Play 商店 → 清除缓存)、你勾选的发行国家/地区是否包含家人所在地区。
审核被拒:隐私政策问题
隐私政策链接打不开,或者内容是明显的模板占位符,或者写了不收集数据但代码里其实在用分析 SDK。三者对不上就会被拒。
审核被拒:敏感权限
后台定位、QUERY_ALL_PACKAGES、精确闹钟这类权限,Play 要求你说明用途并可能需要额外声明表单。vibe-coded 项目经常因为依赖库顺手带进来的权限中招。检查合并后的清单文件,把不需要的权限显式移除。
签名搞丢了
如果只是上传密钥丢了,可以在 Play Console 里申请重置上传密钥。如果是没有启用 Play 应用签名、应用签名密钥也丢了,就比较麻烦。所以密钥文件至少存两个地方。
targetSdk 太低被拒
Play 每年会提高新应用和更新必须满足的最低 targetSdk,以官方文档当前要求为准。改完之后要在真机上回归测一遍。
免费改付费
改不了。一开始想清楚。
Vibe-coded 项目特有的坑
AI 生成的代码常常把密钥写在前端、把 usesCleartextTraffic 打开、把调试日志留在 release 包里。打包前搜一遍 apiKey、secret、password、http://,把这些清干净。
---
下一步建议
跑通之后,可以把重复劳动自动化掉:
- 用 GitHub Actions 在打 tag 时自动构建 AAB,省掉每次本地打包
- 用 fastlane 之类的工具把 AAB 自动上传到指定轨道,连 Console 页面都不用开
- 打开 Play Console 自带的 Android vitals,家人在用的时候崩溃会直接报到这里,比等他们发微信说"打不开"快得多
- 给版本号定个规矩,比如
0.x阶段随便改,1.0之后只在功能稳定时递增 - 三年五年后回头看,最值钱的是那份 keystore 备份和一份写清楚"这个应用当初为谁做的什么"的 README
如果哪天想让更多朋友用,再把封闭测试扩到正式发布即可,代码和包都不用重做——这也是先在测试轨道起步的好处。
