Android - 生产环境中 Google Play 计费库出现错误

7
我在Google Play商店发布了一个Android应用程序,上周发布了一个新的更新版本,只是为了修复一些小问题。从我将更新版本添加到Play商店开始,我就可以在Firebase Crashlytics上看到当有人尝试购买应用程序功能时存在问题。
在将更新版本发布到生产之前,我将该应用程序添加到Alpha测试中,以确保InAppPurchase正常工作,结果也是正常的。
当其他人尝试购买应用程序功能时,我会看到抛出了这个致命异常:
Fatal Exception: java.lang.IllegalArgumentException: SKU cannot be null.
   at com.android.billingclient.api.BillingFlowParams$Builder.build(com.android.billingclient:billing@@3.0.0:23)

SKU仍在我的“管理产品”列表中处于活动状态。
这是我在片段中使用的代码来初始化计费客户端:
        billingClient = BillingClient.newBuilder(getActivity())
            .enablePendingPurchases()
            .setListener(purchasesUpdatedListener)
            .build();

这是我用来启动连接的代码:

billingClient.startConnection(new BillingClientStateListener() {
        @Override
        public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
            Log.d(TAG, "Connection finished");
            if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                // The BillingClient is ready. You can query purchases here.
                List<String> skuList = new ArrayList<>();
                skuList.add("unlock_keyboard");
                SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
                params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP);
                billingClient.querySkuDetailsAsync(params.build(),
                        new SkuDetailsResponseListener() {
                            @Override
                            public void onSkuDetailsResponse(@NonNull BillingResult billingResult,
                                                             List<SkuDetails> skuDetailsList) {
                                // Process the result.
                                if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && skuDetailsList != null) {
                                    for (Object skuDetailsObject : skuDetailsList) {
                                        skuDetails = (SkuDetails) skuDetailsObject;
                                        sku = skuDetails.getSku();


                                    }
                                    Log.d(TAG, "i got response");
                                    Log.d(TAG, String.valueOf(billingResult.getResponseCode()));
                                    Log.d(TAG, billingResult.getDebugMessage());
                                }
                            }
                        });
            }
        }

这是我用来处理购买的代码:

PurchasesUpdatedListener purchasesUpdatedListener = new PurchasesUpdatedListener() {
        @Override
        public void onPurchasesUpdated(@NonNull BillingResult billingResult, @Nullable List<Purchase> list) {
            if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && list != null) {
                for (Purchase purchase : list) {
                    handlePurchase(purchase);
                    Log.d(TAG, "Purchase completed" + billingResult.getResponseCode());
                }
            } else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) {
                Log.d(TAG, "User Canceled" + billingResult.getResponseCode());
            } else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
                if ("unlock_keyboard".equals(sku)) {
                    KeyboardAlreadyPurchasedConfirmation();
                }
                Log.d(TAG, "Item Already owned" + billingResult.getResponseCode());
            }
        }
    };

为了启动计费流程,用户必须在对话框内点击一个按钮。以下是代码:
        builder.setPositiveButton(
            getString(R.string.purchase_keyboard),
            new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int id) {

                    sku = "unlock_keyboard";
                    BillingFlowParams flowParams = BillingFlowParams.newBuilder()
                            .setSkuDetails(skuDetails)
                            .build();
                    billingClient.launchBillingFlow(Objects.requireNonNull(getActivity()), flowParams);

                }

            });

在我应用的旧版本中,从未发生过这种情况,只是在新升级后才开始发生。我只需要知道可能是什么导致了这个问题,可能是我的代码出了问题还是Google Play服务方面的问题?我需要指出,这在不同设备和不同Android版本上都发生了。
非常感谢您提前的帮助。

请在您的flowParams中删除.setSkuDetails(skuDetails)并添加.setSku(YOUR_SKU)和.setType(BillingClient.SkuType.INAPP)。 - mili2501
你的 List<SkuDetails> skuDetailsList 中是否有商品? - isthemartin
你解决了这个问题吗?我也遇到了同样的问题。对我来说,它是在我将Google Play计费库升级到3.0版本之后开始出现的。 - L3n95
很不幸,我还没有解决这个问题。我尝试了许多不同的方法,比如从一些国家中删除应用程序,认为这可能只是Google Play服务在这些国家不再工作的问题,但这个解决方案并没有起作用。事实上,在我将Google Play计费库升级到3.0版本后,有几个用户能够购买产品。所以这让我想到,问题不在于我的代码,而是与Google Play计费库有关的问题,这是谷歌没有告知我们的问题,这非常令人失望。 - Alexandru Dumitru
有没有解决方案? - Eren Tüfekçi
很遗憾,我找不到任何解决这个问题的方法。我尝试了不同的方法,但都没有奏效。更令人沮丧的是,这些错误会影响用户体验。当用户点击“购买此物品”按钮时,应用程序有时会崩溃,因此我开始收到负面评价。我正在考虑暂时从我的应用程序中删除Google计费库,以改善用户体验。如果有人有关于如何解决此问题的想法,请在此处留言。我看到,我不是唯一一个对如何解决此问题感兴趣的人。 - Alexandru Dumitru
3个回答

1
我还没有解决这个问题,但我找到了一种方法来减少这个库升级产生的错误数量。
我所做的是将 Google 计费库从版本 3.0.1 降级到版本 2.1.0,尽管我仍然会在 Firebase 中收到一些错误("SKU 为空"),但大多数用户现在能够购买产品。
此外,我实现了一个方法,每当活动首次打开时无法启动 Google 计费库连接时调用该方法,更确切地说,这是重新启动计费连接方法。
如果您遇到相同的问题,我建议您至少尝试同样的方法,因为似乎 Google 计费库仍然存在一些需要修复的问题。
1. 在 build.gradle(app) 中添加以下行:
implementation 'com.android.billingclient:billing:2.1.0'

2. 在AndroidManifest.xml文件中添加BILLING权限,因为旧版本的这个库仍然需要它:

<uses-permission android:name="com.android.vending.BILLING" />

创建一个重新启动计费连接的方法:
public void restartBillingConnection() {
    billingClient = BillingClient.newBuilder(Objects.requireNonNull(getActivity())).enablePendingPurchases().setListener(ChooseOptionsFragment.this).build();

    billingClient.startConnection(new BillingClientStateListener() {@Override
    public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
        Log.d(TAG, "Connection finished");
        if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
            // The BillingClient is ready. You can query purchases here.
            List < String > skuList = new ArrayList < >();
            skuList.add(ITEM_SKU_AD_REMOVAL);
            SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
            params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP);
            billingClient.querySkuDetailsAsync(params.build(), new SkuDetailsResponseListener() {@Override
            public void onSkuDetailsResponse(@NonNull BillingResult billingResult, List < SkuDetails > skuDetailsList) {
                if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && skuDetailsList != null) {
                    for (Object skuDetailsObject: skuDetailsList) {
                        skuDetails = (SkuDetails) skuDetailsObject;
                        sku = skuDetails.getSku();
                        String price = skuDetails.getPrice();
                        if (ITEM_SKU_AD_REMOVAL.equals(sku)) {
                            skuPrice = price;
                            BillingFlowParams flowParams = BillingFlowParams.newBuilder().setSkuDetails(skuDetails).build();
                            billingClient.launchBillingFlow(Objects.requireNonNull(Objects.requireNonNull(getActivity())), flowParams);
                        }
                        else {
                            Log.d(TAG, "Sku is null");
                        }

                    }
                    Log.d(TAG, "i got response");
                    Log.d(TAG, String.valueOf(billingResult.getResponseCode()));
                    Log.d(TAG, billingResult.getDebugMessage());
                }
                else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.ERROR) {
                    Toast.makeText(getActivity(), "Error in completing the purchase!", Toast.LENGTH_SHORT).show();
                }
            }
            });
        }

        else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.SERVICE_TIMEOUT) {
            Toast.makeText(getActivity(), "Service timeout!", Toast.LENGTH_SHORT).show();
        }
        else {
            Toast.makeText(getActivity(), "Failed to connect to the billing client!", Toast.LENGTH_SHORT).show();
        }

    }@Override
    public void onBillingServiceDisconnected() {
        restartBillingConnection();
    }
    });
}

4. 确保在Google Billing服务断开连接时调用此方法:

@Override
    public void onBillingServiceDisconnected() {
        restartBillingConnection();
    }

希望此解决方案能帮助您暂时解决问题。如果您有其他完全解决方法,请在此帖子中留下答案。

0

我认为这可能是由于并发(即:购买按钮被点击两次)而发生的。

尝试在整个结算流程运行时禁用“购买”按钮。


0

我遇到了同样的问题,或者可能只是类似的问题。但在我的情况下,我已经找到了原因。我可以通过以下方式重现它:

  • 禁用互联网连接
  • 重新安装应用程序
  • 尝试启动应用内购买,如此处所述:Link
    • 似乎与您的例程相似

querySkuDetailsAsync对于每个项目都返回null,当调用initiatePurchaseFlow时,会将null传递给它和BillingFlowParams.Builder。我认为这已经改变了,在之前它没有抛出异常,而是以不同的方式处理它。现在我通过检查Map中的项目是否为null来修复了这个问题,并显示需要互联网连接的警告。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接