何时会广播 com.android.vending.billing.PURCHASES_UPDATED?
结论
com.android.vending.billing.PURCHASES_UPDATED 是旧版 Google Play Billing 机制中的通知广播。当 Google Play 发现本机当前应用的购买记录发生变化时,可能会发送这个广播。常见情况包括购买完成、购买被取消或退款,以及购买状态同步后发生变化。
这个广播只说明购买数据可能已经变化,并不能可靠地指出具体订单或变化内容。收到广播后,应用应重新调用 Billing API 查询购买记录并核对状态,不能将广播本身视为购买凭证。
新功能应使用 Google Play Billing Library 的 PurchasesUpdatedListener 和 queryPurchasesAsync(),不应再把这个广播作为核心业务入口。
广播通常在什么情况下出现
使用旧版 In-app Billing API 时,以下情况可能触发该广播:
- 用户完成应用内购买。
- Google Play 将处于等待状态的交易更新为成功或失败。
- 购买被取消、撤销或退款,且状态已经同步到设备。
- Google Play 重新同步设备上的购买记录,并发现当前应用的购买数据发生变化。
- 用户通过应用外部的 Play 流程完成购买,随后记录同步到当前设备。
需要注意的是,这些情况只是可能触发广播。该 action 属于旧版 Billing 工作流,Google 并未承诺它能覆盖所有购买状态变化,也不保证每次变化都会立即发送广播。应用未运行、设备离线、Play 商店版本不同以及系统后台限制,都可能影响接收时间。
跨设备购买和服务端退款尤其不应依赖本地广播来及时发现。如果业务对可靠性要求较高,应同时使用服务端校验和 Google Play Real-time Developer Notifications。
收到广播后应该怎么做
处理流程如下:
- Receiver 收到
PURCHASES_UPDATED。 - 不读取或信任广播中没有明确约定的额外字段。
- 通过 Billing API 重新查询当前购买记录。
- 根据
purchaseToken、商品 ID 和购买状态更新业务数据。 - 对有效购买执行服务端验证、确认或消耗操作。
- 确保处理逻辑幂等,避免重复发放权益。
广播只是一个刷新信号。客户端应以查询结果作为购买状态的依据。
旧版 Receiver 示例
如果旧项目仍依赖该 action,可以定义一个 Receiver,只用它触发购买查询:
public final class PurchasesUpdatedReceiver extends BroadcastReceiver {
public static final String ACTION_PURCHASES_UPDATED =
"com.android.vending.billing.PURCHASES_UPDATED";
private final Runnable refreshPurchases;
public PurchasesUpdatedReceiver(Runnable refreshPurchases) {
this.refreshPurchases = refreshPurchases;
}
@Override
public void onReceive(Context context, Intent intent) {
if (ACTION_PURCHASES_UPDATED.equals(intent.getAction())) {
refreshPurchases.run();
}
}
}
动态注册示例:
private PurchasesUpdatedReceiver purchasesReceiver;
@Override
protected void onStart() {
super.onStart();
purchasesReceiver =
new PurchasesUpdatedReceiver(this::queryCurrentPurchases);
IntentFilter filter = new IntentFilter(
PurchasesUpdatedReceiver.ACTION_PURCHASES_UPDATED
);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
registerReceiver(
purchasesReceiver,
filter,
Context.RECEIVER_EXPORTED
);
} else {
registerReceiver(purchasesReceiver, filter);
}
}
@Override
protected void onStop() {
unregisterReceiver(purchasesReceiver);
purchasesReceiver = null;
super.onStop();
}
由于广播来自应用进程之外,在 Android 13 及以上版本中动态注册时,需要根据实际发送方式正确设置 Receiver 标志。旧项目能否继续收到该广播,还会受到 Billing 实现、Google Play 商店版本和系统限制的影响,因此需要在目标设备和目标版本上验证。
使用当前 Billing Library 的实现
新代码应直接监听购买流程的结果:
PurchasesUpdatedListener purchasesUpdatedListener =
(billingResult, purchases) -> {
if (billingResult.getResponseCode()
== BillingClient.BillingResponseCode.OK
&& purchases != null) {
for (Purchase purchase : purchases) {
handlePurchase(purchase);
}
} else if (billingResult.getResponseCode()
== BillingClient.BillingResponseCode.USER_CANCELED) {
// 用户取消购买
} else {
// 记录或处理其他错误
}
};
建立连接后,还要主动查询已有购买,以覆盖应用重启、重新回到前台或回调未到达等情况:
QueryPurchasesParams params = QueryPurchasesParams.newBuilder()
.setProductType(BillingClient.ProductType.INAPP)
.build();
billingClient.queryPurchasesAsync(
params,
(billingResult, purchases) -> {
if (billingResult.getResponseCode()
== BillingClient.BillingResponseCode.OK) {
for (Purchase purchase : purchases) {
handlePurchase(purchase);
}
}
}
);
查询订阅商品时,需要另外使用:
BillingClient.ProductType.SUBS
PurchasesUpdatedListener 主要接收当前 Billing 流程的结果,queryPurchasesAsync() 则用于恢复和同步购买记录,两者不能互相替代。
注意事项
- 收到广播后,不要直接发放商品或会员权益。
- 广播可能重复发送,购买处理必须基于
purchaseToken实现幂等控制。 - 客户端结果可能被篡改,重要权益应通过服务端验证购买。
PURCHASED、PENDING等状态需要分别处理。购买处于PENDING状态时,不能提前发放权益。- 需要确认的购买应及时调用
acknowledgePurchase();消耗型商品则按业务要求调用consumeAsync()。 - 退款、撤销和跨设备状态变化不适合仅通过本地 Receiver 追踪。
- 不要假设广播一定包含订单号、商品 ID 或最终购买状态。
- 如果某个旧 SDK 明确要求注册该 Receiver,可以将其作为 SDK 的兼容入口,但仍需保留应用启动和恢复前台时主动查询购买记录的逻辑。
备注:内容仅供参考。
