PAYATHON 2026

Google Billing 中 handlePurchase 的 billingClient 问题

数据小许

结论

billingClient 不能在当前匿名内部类中直接引用,因为它的局部变量声明位于 purchaseUpdateListener 之后,在匿名内部类所在的位置还不可见。

建议把 BillingClient 保存为 Billing 类的成员变量,并在监听器中通过 Billing.this.billingClient 访问。这样也方便统一管理连接和资源释放。

推荐实现

public class Billing {

    private final Context context;
    private final List<String> skuList;
    private BillingClient billingClient;

    public Billing(Context context, List<String> skuList) {
        this.context = context.getApplicationContext();
        this.skuList = skuList;
    }

    public interface Callback {
        void onSkuDetailsList(
                BillingClient billingClient,
                List<SkuDetails> skuDetailsList
        );

        void onResponse(Purchase purchase);
        void onError();
    }

    public void myPurchase(final Callback callback) {
        PurchasesUpdatedListener purchaseUpdateListener =
                new PurchasesUpdatedListener() {
                    @Override
                    public void onPurchasesUpdated(
                            BillingResult billingResult,
                            List<Purchase> purchases
                    ) {
                        if (billingResult.getResponseCode()
                                == BillingClient.BillingResponseCode.OK
                                && purchases != null) {

                            for (Purchase purchase : purchases) {
                                callback.onResponse(purchase);
                                handlePurchase(Billing.this.billingClient, purchase);
                            }
                        } else {
                            callback.onError();
                        }
                    }
                };

        billingClient = BillingClient.newBuilder(context)
                .enablePendingPurchases()
                .setListener(purchaseUpdateListener)
                .build();
    }

    private void handlePurchase(
            BillingClient billingClient,
            Purchase purchase
    ) {
        if (billingClient == null) {
            return;
        }

        // 在这里验证并处理购买结果。
    }
}

Billing.this.billingClient 表示访问外层 Billing 对象的成员变量。这里只写 billingClient 通常也能正常工作,但显式写出外层对象后,变量来源更清楚。

为什么原代码无法编译

Java 的局部变量只有从声明位置开始才可见。原代码的顺序如下:

PurchasesUpdatedListener purchaseUpdateListener = ... {
    handlePurchase(billingClient, purchase);
};

final BillingClient billingClient = ...;

编译器处理匿名内部类时,局部变量 billingClient 尚未声明,因此无法解析这个名称。

直接交换两段代码同样无法解决,因为创建 BillingClient 时还要把 purchaseUpdateListener 传给 setListener():

.setListener(purchaseUpdateListener)

两者在初始化时相互依赖,因此把客户端改为成员变量最直接。

也可以让监听器调用实例方法

如果 handlePurchase() 只供当前 Billing 对象内部使用,就不必反复传入 BillingClient:

private void handlePurchase(Purchase purchase) {
    if (billingClient == null) {
        return;
    }

    // 使用成员变量 billingClient 处理 purchase。
}

然后在监听器中调用:

handlePurchase(purchase);

这种写法更简洁,因为 BillingClient 本来就是这个类需要管理的状态。

注意事项

构建 BillingClient 只是创建了对象。查询商品、发起购买或确认购买之前,通常还要调用 startConnection() 建立连接,并等待 onBillingSetupFinished() 返回成功后再继续操作。

还需要注意以下几点:

  • 不要在每次调用 myPurchase() 时都无条件创建新的 BillingClient,除非旧实例已经正确关闭。
  • 不再使用客户端时,调用 billingClient.endConnection()。
  • onPurchasesUpdated() 不应只把 Purchase 返回给界面层。购买成功后,还要根据商品类型和业务流程进行验证、确认或消耗。
  • 确认商品权益已经正确发放后,才能执行购买确认。
  • 如果使用较新的 Google Play Billing Library,部分类型或构建参数可能与示例不同,应以项目实际依赖版本的 API 为准。

备注:内容仅供参考。