in-app subscription 每日 inventory 查询是否有限制?
结论
mHelper.queryInventoryAsync(mGotInventoryListener) 不会计入 Google Play Developer API 的每日请求配额。它由 Android 设备上的 Google Play Billing service 处理,而服务器通过 HTTP 调用的 Google Play Developer API 是另一套接口。
即使没有后端服务器,应用也可以在启动或恢复运行时查询用户当前拥有的订阅。一般不需要为了避开 200k quota 而刻意降低查询频率。
为什么不会占用 Developer API 配额
这两类查询走的是不同路径:
queryInventoryAsync(...)是客户端 In-app Billing 接口,通过设备上的 Google Play service 查询购买记录。购买数据通常会缓存在本地。- Google Play Developer API 是服务器端接口,例如查询
purchases.subscriptions。它需要开发者账号授权,并受 Google Cloud 项目配额限制。
只有服务器直接请求 Google Play Developer API 时,才会消耗对应的配额。客户端调用 queryInventoryAsync(...) 不使用这项配额。应用没有后端服务器时,实际上也不会调用 Developer API。
queryInventoryAsync(...) 除了查询已有购买记录,还可能根据传入的参数查询商品详情。这些操作虽然不占用 Developer API quota,但也不宜在短时间内反复执行。
推荐的查询时机
可以在这些时候刷新订阅状态:
- 应用启动并成功连接 Billing service 后
- 应用从后台回到前台时
- 用户完成购买后
- 用户点击”恢复购买”或”刷新订阅状态”时
如果应用频繁触发 onResume(),要避免多个查询同时运行。可以设置一个”查询进行中”标志,也可以加上较短的刷新间隔。这样做是为了减少重复工作,避免生命周期回调引起并发问题,与 Developer API 配额无关。
private boolean inventoryQueryInProgress = false;
private long lastInventoryQueryTime = 0L;
private static final long MIN_QUERY_INTERVAL_MS = 30_000L;
private void refreshInventoryIfNeeded() {
if (inventoryQueryInProgress) {
return;
}
long now = System.currentTimeMillis();
if (now - lastInventoryQueryTime < MIN_QUERY_INTERVAL_MS) {
return;
}
inventoryQueryInProgress = true;
lastInventoryQueryTime = now;
mHelper.queryInventoryAsync((result, inventory) -> {
inventoryQueryInProgress = false;
if (result.isFailure()) {
// 保留当前状态,稍后重试;不要直接把用户降级为未订阅
return;
}
Purchase purchase = inventory.getPurchase(SUBSCRIPTION_SKU);
boolean hasSubscription = purchase != null;
updateSubscriptionAccess(hasSubscription);
});
}
MIN_QUERY_INTERVAL_MS 不是 Google Play 规定的限制,而是应用用来防止页面切换、屏幕旋转或生命周期回调造成短时间重复查询的策略。具体间隔可以根据应用结构调整,也可以只用 inventoryQueryInProgress 防止重复进入。
订阅状态不能只看”取消”
用户取消自动续订后,通常仍能使用订阅权益,直到当前结算周期结束。因此,“已取消续订”不代表订阅立即失效。客户端查询时,应关注该订阅当前是否仍属于用户,而不是用户有没有关闭自动续订。
还需要处理这些情况:
- 查询失败或 Billing service 暂时不可用
- 设备离线,返回的数据可能不是最新状态
- 退款、撤销或账号切换尚未同步到本地
- 上一次查询尚未完成,应用又发起了新查询
- Billing service 尚未连接,应用已经开始查询
查询失败后,不建议立即撤销已经授予的订阅权益。更稳妥的处理方式是保留上一次成功确认的状态,等 Billing service 恢复后再查询。
没有后端服务器时的限制
只使用客户端查询可以满足一般的订阅权益恢复需求,但在安全性和及时性上不如服务端验证:
- 客户端环境可能被修改,无法完全信任。
- 离线缓存可能在短时间内过期。
- 无法使用服务端购买验证或实时开发者通知。
- 退款、撤销和续订状态发生变化后,客户端可能无法及时感知。
如果订阅控制的是高价值内容、跨设备服务或服务端资源,最终应由后端通过 Google Play Developer API 验证购买令牌。到了这一步,才需要考虑 API quota、验证结果缓存以及通过通知同步状态。
此外,IabHelper 和 queryInventoryAsync(...) 是较早的示例封装。新项目应优先使用当前版本的 Google Play Billing Library及其购买查询接口,例如 queryPurchasesAsync(...)。无论使用旧接口还是新接口,客户端 Billing 查询和 Google Play Developer API 的服务端配额始终是两个不同的概念。
备注:内容仅供参考。
