关于 gplay-preflight
gplay-preflight 是用于上传前扫描本地 Android APK 和 AAB 构建产物的技能,可离线检查发布风险、权限、原生库、机密信息及可选的商店元数据,并提供 CI 门禁和 JSON 报告。
主要功能
- 01离线扫描 APK 和 AAB
- 02检查清单与权限
- 03检查原生库和大小
- 04检测机密信息与 SDK
- 05可选验证商店元数据
- 06JSON 报告与 CI 门禁
适用场景
- 01上传前检查 Android 构建产物
- 02在 CI 中阻止存在严重问题的发布
- 03检查商店文案和图片素材
- 04使用 JSON 工具分析发现项
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ tamtom/ gplay-cli-skills --skill gplay-preflight安装后在 Claude Code 中输入/gplay-preflight确认已加载。
详细介绍
gplay preflight — 离线构建扫描
gplay preflight 会检查本地 .aab 或 .apk 并报告发现的问题。它不会调用任何 API,也不需要凭据,因此可以在任何机器、任何 CI 任务中运行,甚至在构件首次接触 Play 之前就能使用。
请在运行 gplay validate(需要身份验证并会调用 API)和上传之前运行它。它可以发现 Play 可能在数小时后才会拒绝的问题,或导致用户设备安装失败的问题。
前提条件
- 磁盘上有已构建的
.aab或.apk。 - 无需其他条件。不需要运行
gplay auth login,不需要--package,也不需要网络。
基本用法
# Scan a bundle
gplay preflight --file app-release.aab
# Also validate the store listing in the same pass
gplay preflight --file app-release.aab --listings-dir ./fastlane/metadata/android
# Machine-readable
gplay preflight --file app-release.aab --output json --pretty
# See what scanners exist
gplay preflight --list-scanners
preflight 如何读取构建
preflight 会完整解码 AndroidManifest.xml——APK 使用二进制 AXML,App Bundle 使用 aapt2 protobuf——因此发现的问题反映的是实际的类型化属性值,而不是基于子字符串的猜测。
解读结果时,这一点很重要:如果某个属性指向资源引用(例如 android:debuggable="@bool/isDebug"),报告会标明无法静态确定,而不是标为 false。在这种情况下,不要将未发现问题视为不存在问题的证明。
激进的代码缩减可能会重命名或移除类,因此未检测到 SDK 并不能证明该 SDK 不存在。不过,检测到的匹配项具有较高置信度。
九个扫描器
每个扫描器都有一个 ID。可使用 --only 选择,或使用 --skip 排除(以逗号分隔)。
manifest
清单级别的正确性检查和阻止发布的标志。
android:debuggable="true"— 错误。发布了可调试构建。android:testOnly="true"— 错误。Play 会直接拒绝上传。- 对于
targetSdk31 及以上版本,包含<intent-filter>且未设置android:exported的组件 — 错误。这会导致 Android 12 及以上版本安装失败,而不是警告。启动器 Activity 不受此限制。 - 设置了
grantUriPermissions的已导出<provider>— 错误。任何应用都可以访问已授权的 URI。 - 在
targetSdk34 及以上版本中,前台服务类型缺少对应权限 — Android 14 会在运行时抛出SecurityException。dataSync类型需要FOREGROUND_SERVICE_DATA_SYNC。 usesCleartextTraffic="true"— 警告;存在networkSecurityConfig时降级为信息。allowBackup、requestLegacyExternalStorage、包名和版本合理性检查。
permissions
- 需要填写 Play 声明表单的受限权限:SMS 和通话记录组、
MANAGE_EXTERNAL_STORAGE、ACCESS_BACKGROUND_LOCATION、QUERY_ALL_PACKAGES、SYSTEM_ALERT_WINDOW、BIND_ACCESSIBILITY_SERVICE、USE_FULL_SCREEN_INTENT、SCHEDULE_EXACT_ALARM等。每条发现都附有 Play 政策文档链接。 - 需要在 Data safety 中披露的敏感权限(信息级别)。
- 旧版存储权限:
targetSdk为 30 及以上版本时,WRITE_EXTERNAL_STORAGE未设置maxSdkVersion;33 及以上版本时使用READ_EXTERNAL_STORAGE。 - 重复和已弃用的权限。
native_libs
- 缺少
arm64-v8a— 错误。自 2019 年起,Play 要求支持 64 位。 - 16 KB 内存页对齐,从实际 ELF 程序头中读取。
targetSdk为 35 及以上版本时为错误,低于 35 时为警告。使用 16 KB 页的 Android 15 设备无法加载未对齐的.so文件。 - 存在
x86,但缺少x86_64;或使用裸armeabi。 - 存在未剥离的
.debug_*/.symtab节 — 浪费下载空间。 extractNativeLibs="true"— 安装占用空间更大。
metadata
需要指定 --listings-dir。 否则会跳过。预期使用 Fastlane 风格的目录结构(<dir>/<locale>/title.txt、<dir>/<locale>/images/...)。
- 标题 ≤ 30 个字符,简短说明 ≤ 80 个字符,完整说明 ≤ 4000 个字符,版本说明 ≤ 500 个字符 — 按 Unicode 字符计数,而非字节。
- 实际像素尺寸:图标 512×512,特色图片 1024×500,宣传图片 180×120,TV 横幅 1280×720。
- 屏幕截图:每边至少 320px,最多 3840px,宽高比最高为 2:1;手机屏幕截图至少 2 张,每种设备外形最多 8 张,每张最大 8 MB。
secrets
错误:私钥块、AWS 访问密钥、Stripe sk_live_、GitHub 令牌、Slack 令牌和 Webhook、SendGrid 密钥、Google OAuth 客户端密钥、OpenAI/Anthropic 密钥、服务账号 JSON、随应用发布的密钥库(.jks、.keystore、.p12、.pfx、.pem、.ppk),以及 .git/ 或 .env 泄露。
警告:Google API 密钥(AIza…)和 JWT。
Google API 密钥有意归为警告而非错误。Android 应用会按设计嵌入 Maps 和 Firebase 密钥。正确做法是在 Cloud Console 中将密钥限制为仅适用于你的包名和签名证书,而不是从二进制文件中移除。不要建议用户删除密钥。
也会扫描 Dex 字节码,因此硬编码的字符串字面量也会被检测到。
billing
检测构建中是否包含第三方支付处理商(Stripe、Braintree、PayPal、Adyen、Razorpay……),单独存在时会发出警告;如果也存在 Play Billing,则会降级为提示。此外,还会标记已声明 com.android.vending.BILLING 但未实现计费功能的情况,以及未随包提供 Play Billing Library 的计费封装库(RevenueCat、Adapty、Qonversion)。
privacy
盘点 40 多种分析、归因和广告 SDK(提示)。核对 AD_ID 权限:当 targetSdk 为 33 或更高、存在广告/跟踪 SDK 但缺少该权限时发出警告,并提示相反的情况。
policy
targetSdkVersion低于 Play 的最低要求。Google 年度上调要求时,如果新的 gplay 版本尚未发布,可通过--min-target-sdk覆盖。- 受限服务:无障碍服务、VPN、设备管理、通知监听器。
- APK 与 AAB 上传格式。
size
下载大小预算(--max-size)、每个 dex 的预算(--max-dex)、dex 碎片化情况,以及按类别划分的载荷明细,并列出最大的条目。
CI 门禁
# Block only on hard blockers
gplay preflight --file app-release.aab --fail-on error
# Stricter: warnings fail too
gplay preflight --file app-release.aab --fail-on warning
# Narrow the gate to the scanners you care about
gplay preflight --file app-release.aab \
--only manifest,permissions,native_libs,secrets \
--fail-on warning
# Faster: skip the secrets scan on very large builds
gplay preflight --file app-release.aab --skip-secrets
退出代码:
| 代码 | 含义 |
|---|---|
| 0 | 没有达到或超过 --fail-on 级别的发现项 |
| 1 | 存在达到或超过 --fail-on 级别的发现项 |
GitHub Actions:
- name: Offline preflight
run: |
gplay preflight \
--file app/build/outputs/bundle/release/app-release.aab \
--listings-dir fastlane/metadata/android \
--fail-on error
JSON 输出
--output json 会输出完整报告:格式、包、版本代码和名称、最低/目标 SDK、各扫描器的运行状态,以及每条发现项的 check、severity、message、entry、hint 和(适用时)ref 政策链接。
# Just the errors
gplay preflight --file app.aab --output json | jq '[.findings[] | select(.severity=="error")]'
# Which scanners were skipped and why
gplay preflight --file app.aab --output json | jq '.scanners[] | select(.skipped)'
preflight 的定位
| 工具 | 需要授权 | 范围 |
|---|---|---|
gplay preflight | 否 | 离线检查构件本身。Manifest、库、密钥、商品详情文件 |
gplay validate | 是 | 发布就绪情况:本地检查,加上实时轨道和商品详情状态 |
gplay checks analyze | 是 | Google Checks 隐私/政策分析,在服务端异步执行 |
gplay release --dry-run | 是 | 针对真实 edit 执行完整流程,并在提交前丢弃 |
按此顺序运行。preflight 成本最低,能发现最多问题。
Agent 行为
- 在执行任何上传或发布命令之前,先运行
gplay preflight --file <artifact>,并在继续操作前报告发现项。 - 只要存在元数据目录,就传入
--listings-dir;否则会跳过metadata扫描器,且不会报告商品详情问题。 - 将
error级别的发现项视为阻断项。除非用户明确同意,否则不要在存在此类问题时上传。 - 不要只报告消息,还要报告
hint和ref字段。提示会说明实际需要更改什么。 - 不要建议删除密钥扫描器发现的 Google API 密钥,应建议限制其使用范围。
- 如果无法解码 Manifest,或构建经过大量混淆,那么没有发现项并不能证明符合要求。应如实说明,不要声称构建没有问题。
- 构造命令前,先通过
gplay preflight --help确认可用标志。 --only和--skip接受以逗号分隔的扫描器 ID;使用gplay preflight --list-scanners获取当前列表。
注意事项
preflight只读取一次归档,并以有界分块的方式流式处理大型条目,因此处理大型包时内存占用保持稳定。- 扫描器 ID 保持稳定;供 JSON 消费者使用的发现项
check名称会在不同版本间保留。 policy扫描器的目标 API 下限是一个常量,Google 每年 8 月都会提高该值。如果该值看起来已过时,请传入--min-target-sdk。