# Google Play 上架待办清单 > 排查时间:2026-08-29。基线产物:`build/app/outputs/flutter-apk/app-release.apk`(1.0.1+4,Channel=CHINA_XIAOMI)。 > 所有结论都是在**构建产物**上实测的(合并后的 AndroidManifest、APK 里的 ELF 段对齐),不是读源码猜的。 --- ## 0. 先决定:要不要申请延期 有两条硬期限是 **2026-08-31**,也就是排查次日。两条都可以在 Play Console 申请延期到 **2026-11-01**。 **建议先去 Play Console 把延期申请了**,再按下面的顺序改。否则 8/31 之后新包传不上去,改了也白改。 --- ## 1. 硬阻塞 ### 1.1 targetSdk 35 → 36(期限 2026-08-31) - 位置:[android/app/build.gradle.kts](android/app/build.gradle.kts) `defaultConfig.targetSdk = 35` - `compileSdk` 已经是 36,改一个数字即可 - ⚠️ 但 Android 16 有行为变更,改完**必须真机回归**: - 强制 edge-to-edge(状态栏/导航栏不再自动留白,页面可能被遮) - 方向与尺寸可调整性 API 变更(大屏/折叠屏) - 前台服务类型校验更严 ### 1.2 Play Billing 7.1.1 → 8.0+(期限 2026-08-31) - 当前:`in_app_purchase_android` 依赖 `com.android.billingclient:billing:7.1.1` - 要升到带 Billing Library 8 的 `in_app_purchase_android` 版本 - ⚠️ BL8 有破坏性 API 变更,是这份清单里**改动量最大**的一项。来不及就务必申请延期。 - 迁移指南:https://developer.android.com/google/play/billing/migrate-gpblv8 ### 1.3 16KB 页对齐(已于 2025-11-01 生效,无延期) 实测 `arm64-v8a` 共 26 个 `.so`,**3 个不合规**(LOAD 段 p_align = 0x1000,需 ≥ 0x4000): | 库 | 来源 | 处理 | |---|---|---| | `libAMapSDK_NAVI_v10_0_800.so` | 高德导航(预编译) | ✅ 已处理——[build.gradle.kts](android/app/build.gradle.kts) 的 `jniLibs.excludes` 在 `Channel=GLOBAL_GOOGLE` 时排除 `**/libAMapSDK*.so` | | `libg722_jni.so` | **自己的源码** | ❌ 待修 | | `libmsbc_jni.so` | **自己的源码** | ❌ 待修 | 后两个来自 [local_plugins/bluetooth_manager/android/src/main/cpp](local_plugins/bluetooth_manager/android/src/main/cpp)(`g722_jni.cpp` / `msbc_jni.c`,CMake 工程),**源码在仓库里,好修**。 根因:该插件的 `android/build.gradle` **没有声明 `ndkVersion`**,所以没继承主工程的 NDK `28.2.13676358`。NDK r28 起默认就是 16KB 对齐,用了老 NDK 才退回 4KB。 两种改法(建议都加,双保险): ```gradle // local_plugins/bluetooth_manager/android/build.gradle android { ndkVersion = "28.2.13676358" // 与主工程一致,r28 默认 16KB 对齐 defaultConfig { externalNativeBuild { cmake { cppFlags "" arguments "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON" // NDK r27+ 支持 } } } } ``` 兜底(任何 NDK 版本都生效),在 `src/main/cpp/CMakeLists.txt` 里给两个 target 加: ```cmake target_link_options(g722_jni PRIVATE "-Wl,-z,max-page-size=16384") target_link_options(msbc_jni PRIVATE "-Wl,-z,max-page-size=16384") ``` ⚠️ 改完要清 `local_plugins/bluetooth_manager/android/.cxx/`(里面有旧的构建缓存),否则可能直接复用旧产物。 **armeabi-v7a 的不合规项不用管**:16KB 只约束 64 位(arm64-v8a / x86_64)。v7a 里的 `libneonui_shared.so`、`libneonuijni_public.so` 是 32 位专有,不影响上架。 **验证方式**(仓库里已有现成脚本): ```bash python3 check_16kb.py build/app/outputs/bundle/release/app-release.aab ``` --- ## 2. 政策风险:支付回退逻辑(优先级仅次于硬阻塞) **这是最容易被下架的一条。** [lib/modules/goods/controllers/goods_base_controller.dart](lib/modules/goods/controllers/goods_base_controller.dart) 的 `_getEnabledPayMethods()`:当后端没下发 `paychannels` 时,回退成展示 **微信 + 支付宝 + PayPal + Google**。 ```dart // 若后端未下发或全部被平台过滤,则回退为展示所有平台支持的方式 if (methods.isEmpty) { final fallback = <_PayMethod>[ _PayMethod.wechat, _PayMethod.alipay, _PayMethod.paypal ]; ... } ``` 数字商品在 Google Play 必须走 Play 结算,摆出微信/支付宝是最典型的下架理由。而且这个回退**恰好在配置缺失时触发**——排查当天测试环境就正处于这个状态(渠道配置没建,`paychannels` 是空的)。 两件事要一起做: 1. **改回退逻辑**:安卓端兜底只留 `google`(iOS 只留 `apple`),不要「拿不到配置就全展示」 2. **后台配置**:console 后台给 `GLOBAL_GOOGLE` 渠道的 `paychannels` 只填 `google` --- ## 3. 权限瘦身 合并后的 AndroidManifest 共 44 条权限,下面这些会被 Play 拦或需要额外声明: | 权限 | 处理建议 | |---|---| | `QUERY_ALL_PACKAGES` | 需提交声明表;通常要求改成 `` 精确列出微信/支付宝包名(来源应是 fluwx / tobias) | | `ACCESS_BACKGROUND_LOCATION` | 需声明表 + 演示视频,审核最严。**先确认是否真需要后台定位**,不需要就删 | | `SYSTEM_ALERT_WINDOW` | 悬浮窗(`floating_ui_plugin`),需说明用途 | | `WRITE_SETTINGS` | 很少被批准,确认是否真需要 | | `SCHEDULE_EXACT_ALARM` | 有闹钟功能可解释,准备好说明 | | `MEDIA_CONTENT_CONTROL` | **建议直接删**——这是系统签名权限,普通应用根本拿不到,声明了也无效,反而显得可疑 | | `GET_TASKS` | **建议直接删**——API 21 起已废弃并被系统忽略 | 源清单里的 `SEND_SMS`、`MANAGE_EXTERNAL_STORAGE` 已经是注释状态,**合并产物里没有**,不用担心。 查合并后清单的命令: ```bash AAPT=$(ls ~/Library/Android/sdk/build-tools/*/aapt2 | tail -1) $AAPT dump permissions build/app/outputs/flutter-apk/app-release.apk | grep '^uses-permission' ``` --- ## 4. 构建流程的坑 - **`.env` 的 `Channel` 必须是 `GLOBAL_GOOGLE`**(当前是 `CHINA_XIAOMI`)。 `./build_channels.sh google` 会自动改;手工跑 `flutter build appbundle` **不会**,那样 AMap 的 `.so` 也不会被排除 → 16KB 检查直接挂。 - **[build_channels.sh](build_channels.sh) 的 `OUTPUT_DIR` 写死成 `/Users/liuxiamin/work/build_outputs`**,本机不存在,脚本会失败。改成本机路径或改成相对路径。 - Google 渠道排除了 AMap `.so`,但**调用代码还在**。要验证海外包点到地图/导航相关功能不会崩。 - 产物是 **AAB 不是 APK**(`build_channels.sh` 的 google 分支已经处理成 `aab`)。 --- ## 5. Play Console 首次上架事项 - Play App Signing(上传密钥用 `android/app/sign/eaimar.keystore`) - 数据安全表单(Data safety) - 内容分级问卷 - 目标受众与内容声明 - 隐私政策 URL —— iOS 那边已在用 `https://yimai.games/privacy.html`,可复用 --- ## 建议的动手顺序 1. **去 Play Console 申请 targetSdk 与 Billing 的延期**(两条都是 8/31 到期) 2. 16KB 对齐(改 1 个 gradle + 1 个 CMakeLists,最确定,改完用 `check_16kb.py` 验) 3. 支付回退逻辑 + 后台 `paychannels` 配置 4. 权限瘦身(先删 `MEDIA_CONTENT_CONTROL` / `GET_TASKS`,再处理 `QUERY_ALL_PACKAGES`) 5. `build_channels.sh` 的 `OUTPUT_DIR` 改本机路径 6. targetSdk 36 + 真机回归 7. Billing 8 迁移(改动最大,放最后) --- ## 附:本次会话的其他遗留(与 Google Play 无关) - **iOS 包 `1.0.1 (5)` 已打好待上传**:`build/ios/ipa/EAIMAR.ipa`。已修掉通讯录缺用途说明(ITMS-90683),并移除了 ATT/IDFA(`NSUserTrackingUsageDescription` 等三个 key、`app_tracking_transparency` 依赖、splash 里的授权请求)。 ⚠️ 打 iOS 包要带上自备的 ExportOptions(`manageAppVersionAndBuildNumber=false`),否则构建号会被 Xcode 自动改掉: ```bash flutter build ipa --release --export-options-plist=/ExportOptions.plist ``` ⚠️ 只要动了 `ios/Runner/Info.plist` 里的 `*UsageDescription`,就必须清一次 DerivedData 与 SwiftPM 清单缓存,否则 `permission_handler` 会按缓存里的旧宏编译,留下无用途说明的系统 API 引用 → 又一次 ITMS-90683: ```bash rm -rf ~/Library/Developer/Xcode/DerivedData/Runner-* rm -rf ~/Library/Caches/org.swift.swiftpm/manifests ~/Library/org.swift.swiftpm/cache ``` - **游客登录版本开关已上线到阿龙测试环境**(app/console/admin 三个服务),但**后台渠道配置还没建**,所以入口目前一定是隐藏的。要验证需在 console 后台「全局默认」作用域下配 App Store 与 荣耀 两条,生效版本填 `1.0.1`。详见 [CLAUDE.md](../../CLAUDE.md) 的「游客登录入口的显隐」一节。 - **两台测试机装的是 `1.0.1 (4)`**(含 ATT 的旧版),需要的话要重装 `(5)`。