Play Billing Library连接与SKU详情查询问题求助
Hey,我来帮你拆解这两个遇到的问题,顺便梳理下正确的Play Billing实现逻辑:
第一个问题:连接成功后查询SKU返回错误码-1
错误码-1对应BillingClient.BillingResponse.ERROR,这是一个通用错误,核心原因是你调用querySkuDetailsAsync的时机太早了。
虽然日志显示onBillingSetupFinished返回了响应码0(连接成功),但BillingClient的初始化是异步操作——startConnection调用后,系统需要和Play Billing服务建立绑定,这个过程不是瞬间完成的。你的handleManagerAndUiReady可能在BillingClient真正进入READY状态之前就执行了查询,此时客户端还没完全准备好处理请求,就会返回这个通用错误。
至于为什么教程里的逻辑可行?大概率是教程里的handleManagerAndUiReady是被连接成功的回调触发的——比如教程代码里会维护一个mIsServiceConnected的状态标记,只有当onBillingSetupFinished把这个标记设为true后,才会调用handleManagerAndUiReady。你可能没注意到这个状态判断的细节,提前调用了查询方法。
第二个问题:检查连接就绪时返回错误码5
错误码5对应BillingClient.BillingResponse.ALREADY_CONNECTED,原因是你重复发起了连接请求,或者在连接正在进行中(CONNECTING状态)时再次调用了startConnection。
Trivial Drive的逻辑是先判断mBillingClient.isReady(),但如果你的代码在isReady()返回false时就直接调用startConnection,而之前的连接请求还没完成(处于CONNECTING状态),就会触发这个错误。Play Billing客户端不允许同时发起多个连接请求。
正确的实现方案
下面是一个符合Google最佳实践的BillingManager实现,能解决上面两个问题:
public class BillingManager implements BillingClientStateListener, SkuDetailsResponseListener { private static final String TAG = "BillingManager"; private final Activity mActivity; private BillingClient mBillingClient; private boolean mIsBillingConnected = false; // 保存待执行的查询任务,避免连接未就绪时丢失请求 private final List<Runnable> mPendingTasks = new ArrayList<>(); public BillingManager(Activity activity) { mActivity = activity; initBillingClient(); } private void initBillingClient() { mBillingClient = BillingClient.newBuilder(mActivity) .setListener(this) .enablePendingPurchases() // 注意:Play Billing 3.0+必须添加这一行 .build(); connectToBillingService(); } private void connectToBillingService() { // 关键:避免重复连接,只有当客户端既未连接也未在连接中时才发起请求 if (mBillingClient.getState() == BillingClient.State.CONNECTED || mBillingClient.getState() == BillingClient.State.CONNECTING) { return; } mBillingClient.startConnection(this); } @Override public void onBillingSetupFinished(@BillingClient.BillingResponse int billingResponse) { if (billingResponse == BillingClient.BillingResponse.OK) { Log.i(TAG, "Billing service connected successfully"); mIsBillingConnected = true; // 连接就绪后,执行所有待处理的查询任务 executePendingTasks(); } else { Log.w(TAG, "Billing setup failed with code: " + billingResponse); mIsBillingConnected = false; // 可选:添加重试逻辑,比如延迟3秒后重新连接 mActivity.getWindow().getDecorView().postDelayed(this::connectToBillingService, 3000); } } @Override public void onBillingServiceDisconnected() { Log.w(TAG, "Billing service disconnected"); mIsBillingConnected = false; // Google推荐:断开连接后自动重试连接 connectToBillingService(); } // 对外暴露的查询SKU方法,自动处理连接状态 public void querySkuDetails(String skuType, List<String> skuList) { Runnable queryTask = () -> { SkuDetailsParams params = SkuDetailsParams.newBuilder() .setSkusList(skuList) .setType(skuType) .build(); mBillingClient.querySkuDetailsAsync(params, this); }; if (mIsBillingConnected && mBillingClient.isReady()) { // 已就绪,直接执行查询 queryTask.run(); } else { // 未就绪,加入待执行队列,等待连接完成后执行 mPendingTasks.add(queryTask); // 确保客户端正在连接中 connectToBillingService(); } } private void executePendingTasks() { for (Runnable task : mPendingTasks) { task.run(); } mPendingTasks.clear(); } @Override public void onSkuDetailsResponse(int responseCode, List<SkuDetails> skuDetailsList) { if (responseCode == BillingClient.BillingResponse.OK && skuDetailsList != null) { for (SkuDetails details : skuDetailsList) { Log.i(TAG, "Loaded SKU: " + details.getSku()); } // 这里可以通过接口回调给UI层更新界面 } else { Log.w(TAG, "SKU details query failed with code: " + responseCode); } } }
关键细节说明:
enablePendingPurchases():这是Play Billing 3.0及以上版本的必填配置,否则客户端初始化会失败,很多开发者容易忽略这一点。- 连接状态判断:在
connectToBillingService中检查当前客户端状态,避免重复发起连接请求,解决错误码5的问题。 - 待执行任务队列:即使UI层提前调用查询方法,也会把任务加入队列,等连接就绪后自动执行,避免错误码-1的问题。
- 自动重连机制:当Billing服务断开时,自动重试连接,符合Google的最佳实践,提升用户体验。
内容的提问来源于stack exchange,提问作者Meep




