You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Play Billing Library连接与SKU详情查询问题求助

Google Play Billing 连接与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);
        }
    }
}

关键细节说明:

  1. enablePendingPurchases():这是Play Billing 3.0及以上版本的必填配置,否则客户端初始化会失败,很多开发者容易忽略这一点。
  2. 连接状态判断:在connectToBillingService中检查当前客户端状态,避免重复发起连接请求,解决错误码5的问题。
  3. 待执行任务队列:即使UI层提前调用查询方法,也会把任务加入队列,等连接就绪后自动执行,避免错误码-1的问题。
  4. 自动重连机制:当Billing服务断开时,自动重试连接,符合Google的最佳实践,提升用户体验。

内容的提问来源于stack exchange,提问作者Meep

火山引擎 最新活动