任何在 Google Play 上卖东西的安卓应用——订阅、一次性解锁、金币包——包里都带着一份 Google Play Billing Library(结算库)。它没有永久生命力:Google 对每个大版本执行固定两年的淘汰时钟,一旦你的版本被淘汰,应用仍能运行,但你再也提交不了更新。截至 2026 年 8 月 31 日,Play 接受的最低版本是 Billing Library 8;所有仍用 7 或更早版本构建的更新,只剩最后一个窗口:2026 年 11 月 1 日的延期截止日。
下面是有操作价值的那一版:淘汰时间表今天到底怎么写的、怎么查清某个 APK 到底带的是哪个版本、从 7 跳到 8 或 9 时哪些 API 改名会让编译直接失败、延期申请表在哪里,以及发包前必须测什么。
四个数字决定这次迁移: 2 年——自 Google 在 2019 年 I/O 公布该政策以来,每个 Play Billing Library 大版本适用的淘汰周期。2026 年 8 月 31 日——新应用与更新必须使用 8 或更高版本的日期(即 v7 的淘汰日)。2026 年 11 月 1 日——同一版本的延期截止日。8——今天 Play 在发布新版本时最低接受的版本号。
1. 今天这条截止线的实际状态
Google 官方 Play Billing Library 版本淘汰页顶部的横幅原文是:「By Aug 31, 2026, all new apps and updates to existing apps must use Billing Library version 8 or later. If you need more time to update your app, you can request an extension until Nov 1, 2026.」这一句就是全部规则,而它包含两半,常被混为一谈。
第一半是发布限制,不是下架:仍带被淘汰版本的应用对已安装用户继续正常工作,但 Play 不接受你的新版本。淘汰问答页把推论写得很明确——不再维护的 APK 不需要更新,而新应用和更新必须使用受支持的版本。所以风险不是「应用从商店消失」,而是「修复包、新的商店信息实验、合规更新,都发不出去」。
第二半是逃生门:延期。它必须在 Play Console 内申请,而且是对着同一个固定日历日批准的,而不是重新起算——所以对 2026 年 9 月下旬仍在 v7 的团队,真正的问题不是「还有没有时间」,而是「延期到底申请了没有」。
2. 淘汰时钟是固定的,并且提前公布
Google 把整张淘汰时间表都公布了,它更应该被当成排期表来读,而不是警告。每个大版本的「新应用与更新」截止日都是 8 月 31 日,延期截止日都是 11 月 1 日,间隔两年:
- 版本 5 —— 新应用与更新截止日 2024 年 8 月 31 日;延期截止日 2024 年 11 月 1 日。
- 版本 6 —— 2025 年 8 月 31 日;延期 2025 年 11 月 1 日。
- 版本 7 —— 2026 年 8 月 31 日;延期 2026 年 11 月 1 日。
- 版本 8 —— 2027 年 8 月 31 日;延期 2027 年 11 月 1 日。
- 版本 9 —— 2028 年 8 月 31 日;延期 2028 年 11 月 1 日。
由此得出两个结论。第一,2025 年就已经落后两个版本的应用现在落后三个,而今天做这件事的工作量与当时一模一样。第二,这是一条永久性的维护事项,不是一次性项目:无论这次选哪个版本,两年内你还会回到这里。把版本升级并入常规发版节奏,比被 Console 警告推着走要便宜得多。
3. 第一步——查清你的构建实际带的是哪个版本
从 build.gradle 开始。结算库以 com.android.billingclient:billing 依赖形式声明,它的版本号就是答案:
dependencies {
def billingVersion = "8.3.0"
implementation "com.android.billingclient:billing:$billingVersion"
}
淘汰问答页补充了一个关键细节:这些依赖只出现在声明了 com.android.vending.BILLING 权限的 APK 里。在多模块项目,或一个由若干应用共用旧模块的产品矩阵里,出问题的是声明了 billing 的那个构建,而不一定是你印象里的那个应用。要审计每一个带 billing 的 bundle,而不是你记得写过支付代码的那几个。
还有第二处检查,能解释「为什么已经升级了警告还在」。Play 是通过合并后的 AndroidManifest.xml 里的 com.google.android.play.billingclient.version 属性来识别版本。如果你已经升级了依赖、Console 却仍报旧版本,官方建议就是确认该属性是否存在——manifest 合并是它丢失的常见原因。
4. 第二步——定目标:版本 8 还是版本 9
官方要求是「version 8 or later」,也就是 8.x 与 9.x 都满足。这个取舍是真实的,应当有意识地做:
- 版本 8 —— 2025 年 6 月 30 日发布,8.x 线以 2025 年 12 月 23 日的 8.3.0 收尾。改动面更小;如果你的代码在 v7 上,编译错误很少且都是机械性的。
- 版本 9 —— 9.0.0 于 2026 年 5 月 19 日发布,9.1.0 于 2026 年 6 月 18 日发布。API 最新,并把
targetSdkVersion更新到 35,这一点需要与你正在处理的 Play target API level 政策要求一起权衡。
如果你也顺便在规划下一个周期:v8 自己的截止日是 2027 年 8 月 31 日,延期到 2027 年 11 月 1 日。今天直接迁到 9.1.0 能买到最长的缓冲期,而 v9 迁移指南本身就是写给从 7 或 8 直接过来的团队的。
5. 第三步——v7 到 v8 的迁移本体
按官方 Play Billing Library 8 迁移指南,升级是三件事加若干可选项。
第一件:改依赖版本。把 com.android.billingclient:billing 指向 8.0.0 或更高(除非有理由,否则用该线的最新补丁版,而不是 .0)。
第二件:处理改名的订阅 API。从 PBL 6 过来的代码编译会直接断在这里,替代项是等价的改名:
setOldSkuPurchaseToken→setOldPurchaseTokensetReplaceProrationMode→setSubscriptionReplacementModesetReplaceSkusProrationMode→setSubscriptionReplacementMode
第三件:改 queryProductDetailsAsync。ProductDetailsResponseListener.onProductDetailsResponse 的签名有变更,你的实现必须跟着改。签名背后的行为变化比签名本身更重要:8.0.0 之前,取不到的商品干脆不返回;PBL 8 会把它们带回,并附带一个新的商品级状态码说明原因——商品不存在,或该用户没有可用优惠。如果你的界面以前是靠「没有返回」来推断「用户不符合资格」,现在必须去读状态码,而不是猜。
PBL 8 同时移除了以下 API(若你的代码仍在调用):queryPurchaseHistoryAsync(改用 Query Purchase History 中列出的替代方案)、querySkuDetailsAsync(改用 queryProductDetailsAsync)、无参数的 enablePendingPurchases()(改用 enablePendingPurchases(PendingPurchaseParams)),以及旧的 queryPurchasesAsync(String skuType, PurchasesResponseListener) 重载。从 v6 或更早直接跳到 v8 的读者,还需要把 enableAlternativeBilling/AlternativeBillingListener/AlternativeChoiceDetails 换成 enableUserChoiceBilling/UserChoiceBillingListener/UserChoiceDetails。
可选项,但值得做:BillingClient.Builder.enableAutoServiceReconnection() 让库自行重建与 Play 结算服务的连接,而不要求你在断连后再次调用 startConnection()——这是安卓上「点了购买按钮没反应」类反馈最常见的来源。同一版本还为预付费方案增加了待处理购买支持,并支持虚拟分期订阅。
6. 第四步——或者一步到位迁到 9.1.0
Play Billing Library 9 迁移指南面向从 7 或 8 过来的团队。除了把依赖升到 9.1.0,反复出现的主题是旧 SKU 词汇彻底退场(BillingClient.SkuType → ProductType、SkuDetails → ProductDetails、SkuDetailsParams → QueryProductDetailsParams、SkuDetailsResponseListener → ProductDetailsResponseListener、getSkuDetailsList/setSkuDetailsList → setProductDetailsParamsList),而其中一条直接落在订阅收入逻辑上:
如果你此前用
queryPurchaseHistoryAsync判断用户是否符合免费试用资格,指南要求改用ProductDetails.getSubscriptionOfferDetails()来判断用户对哪些优惠符合资格。
v9 里还有三处小改动容易被漏掉。Play 商店被阻止时的处理变了:当 Play 商店应用本身被系统阻止(例如 OEM 定制的儿童模式),返回码从 ERROR 变为 BILLING_UNAVAILABLE,并附带「Play Store is blocked」的调试信息,且该行为需要 androidx.core 1.9 或更高版本。其次,DeveloperProvidedBillingDetails.getLinkUri() 现在标注为 @Nullable,任何未做 null 与空字符串判断就启动该 URI 的代码都是现成的崩溃点。第三,9.0.0 为「选择性涨价」增加了应用内提示,用户不必离开应用即可确认涨价——官方限定同一提示最多每 7 天展示一次。
7. 第五步——真需要时,才用延期申请
如果你的应用仍在发布不受支持的版本,Play Console 会给出警告。延期是从该警告的详情页申请的:打开 Policy status(政策状态),进入该警告,使用那里链接的延期表单。按官方说明,这是唯一渠道——没有邮件申请,对应版本在已公布的 11 月 1 日之后也没有第二延期。
把它当成一个已经固定的截止日之内的发货缓冲,而不是额外跑道。即使提交了延期,工作仍必须在 2026 年 11 月 1 日前完成;此后基于 v7 的新版本不再被接受——包括你可能正在等的那次、与本次政策完全无关的合规发版。
8. 第六步——发包前的验证
结算类回归不会以崩溃的形式出现,它表现为「付费用户买不了」。发布进正式轨道之前:
- 在一台真正装了 Play 商店的设备上,用许可测试账号走一遍真实购买、订阅升降级与恢复购买——不带 Play 商店的模拟器不会覆盖你刚改动的连接处理逻辑。
- 确认商品详情请求现在会返回取不到的商品及状态码,且界面能区分「商品不存在」与「该用户无可用优惠」。
- 确认发布后 Play Console 的淘汰警告消失,且合并后的 manifest 中存在
com.google.android.play.billingclient.version属性。 - 用分阶段发布放量,让潜在回归只触达一部分用户,并准备好一个更高版本号随时替换。
9. 新版本在收入侧给了什么
迁移是实打实的工作量,所以也该算清新版解锁了什么。PBL 8.0.0 把「in-app items」改名为 一次性商品,并为其增加多个购买选项与优惠,减少了「同一个东西按不同价位卖」所需创建的商品数量。它还为 launchBillingFlow() 增加了子返回码:PAYMENT_DECLINED_DUE_TO_INSUFFICIENT_FUNDS、USER_INELIGIBLE(用户不满足所配置的订阅优惠资格要求),以及默认值 NO_APPLICABLE_SUB_RESPONSE_CODE——「出了点问题」与「请先给卡充值」之间的差别就在这些码里。
2025 年 12 月的 8.2.x 与 8.3.0 增加了外部内容链接、外部优惠与外部支付 API,9.1.0 增加了 Billing Choice 相关 API。若你的路线图包含这些项目,升级版本是前置条件而不是另一个独立项目——这也是「既然已经在这段代码里,就顺便迁到 9.1.0」的实际理由。