首页 > 解决方案 > Java - app crash when using in app purchase

问题描述

So I have experienced some issue with in app purchase in my app.

I have started working on an old (8 months old) project I had earlier, but I am having some issue with app purchase. The app is already live on Play Store, so in app purchase is active.

In build.gradle (:app) I have changed from:

dependencies {
  implementation 'com.android.billingclient:billing:2.2.1'

to this:

dependencies {
  def billing_version = "3.0.0" // In App Purchase
      implementation "com.android.billingclient:billing:$billing_version"

This is the full code of my UpgradeActivity.java:

package com.pinloop.testproj;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClientStateListener;
import com.android.billingclient.api.BillingFlowParams;
import com.android.billingclient.api.BillingResult;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.android.billingclient.api.SkuDetails;
import com.android.billingclient.api.SkuDetailsParams;
import com.android.billingclient.api.SkuDetailsResponseListener;

import java.util.ArrayList;
import java.util.List;

public class UpgradeActivity extends AppCompatActivity implements PurchasesUpdatedListener {

    private Button upgradeButton;
    private TextView restoreButton;

    private BillingClient billingClient;
    private List skuList = new ArrayList();

    private String sku = "com.pinloop.testproj.pro";

    private SkuDetails mSkuDetails;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_upgrade);

        upgradeButton = (Button) findViewById(R.id.upgradeButton);
        upgradeButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                upgradeAction();
            }
        });

        // Check In App Purchase

        upgradeButton.setEnabled(true);
        skuList.add(sku);


        Boolean pro = getBoolFromPref(this,"myPref", sku);
        if (pro)
        {
            Toast.makeText(this, "you are a premium user", Toast.LENGTH_SHORT).show();
            //upgradeButton.setVisibility(View.INVISIBLE);
            upgradeButton.setText(R.string.you_are_premium);

        }
        else
        {
            Toast.makeText(this, "not pro", Toast.LENGTH_SHORT).show();
            setupBillingClient();
        }
    }

    private void upgradeAction() {
        BillingFlowParams billingFlowParams = BillingFlowParams
                .newBuilder()
                .setSkuDetails(mSkuDetails)
                .build();
        billingClient.launchBillingFlow(UpgradeActivity.this, billingFlowParams);
    }


    // In App Handler:

    private void setupBillingClient() {
        billingClient = BillingClient.newBuilder(this).enablePendingPurchases().setListener(this).build();
        billingClient.startConnection(new BillingClientStateListener(){

            @Override
            public void onBillingSetupFinished(BillingResult billingResult) {
                if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                    // The BillingClient is setup successfully
                    loadAllSKUs();
                }
            }

            @Override
            public void onBillingServiceDisconnected() {
                // Try to restart the connection on the next request to
                // Google Play by calling the startConnection() method.
            }
        });
    }

    private void loadAllSKUs() {
        Toast.makeText(this, "loadAllSKUs", Toast.LENGTH_SHORT).show();

        if (billingClient.isReady())
        {
            Toast.makeText(this, "billingclient ready", Toast.LENGTH_SHORT).show();
            SkuDetailsParams params = SkuDetailsParams.newBuilder()
                    .setSkusList(skuList)
                    .setType(BillingClient.SkuType.INAPP)
                    .build();

            billingClient.querySkuDetailsAsync(params, new SkuDetailsResponseListener() {
                @Override
                public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
                    Toast.makeText(UpgradeActivity.this, "inside query" + billingResult.getResponseCode(), Toast.LENGTH_SHORT).show();
                    if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
                            && !skuDetailsList.isEmpty())
                    {
                        for (Object skuDetailsObject : skuDetailsList) {
                            final SkuDetails skuDetails = (SkuDetails) skuDetailsObject;
                            Toast.makeText(UpgradeActivity.this, "" + skuDetails.getSku(), Toast.LENGTH_SHORT).show();

                            if (skuDetails.getSku() == sku)
                                mSkuDetails = skuDetails;
                            upgradeButton.setEnabled(true);

                            upgradeButton.setOnClickListener(new View.OnClickListener() {
                                @Override
                                public void onClick(View v) {
                                    BillingFlowParams billingFlowParams = BillingFlowParams
                                            .newBuilder()
                                            .setSkuDetails(skuDetails)
                                            .build();
                                    billingClient.launchBillingFlow(UpgradeActivity.this, billingFlowParams);

                                }
                            });
                        }
                    }
                }
            });
        }
        else
            Toast.makeText(this, "billingclient not ready", Toast.LENGTH_SHORT).show();

    }

    @Override
    public void onPurchasesUpdated(BillingResult billingResult, @Nullable List<Purchase> purchases) {
        int responseCode = billingResult.getResponseCode();
        if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
                && purchases != null) {
            for (Purchase purchase : purchases) {
                handlePurchase(purchase);
            }
        }
        else
        if (responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
            // Handle an error caused by a user cancelling the purchase flow.
            //Log.d(TAG, "User Canceled" + responseCode);
        }
        else if (responseCode == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
            ///mSharedPreferences.edit().putBoolean(getResources().getString(R.string.pref_remove_ads_key), true).commit();
            ///setAdFree(true);
            setBoolInPref(this,"myPref",sku, true );
        }
        else {
            //Log.d(TAG, "Other code" + responseCode);
            // Handle any other error codes.
        }
    }

    private void handlePurchase(Purchase purchase) {
        if (purchase.getSku().equals(sku)) {
            ///mSharedPreferences.edit().putBoolean(getResources().getString(R.string.pref_remove_ads_key), true).commit();
            ///setAdFree(true);
            setBoolInPref(this,"myPref",sku, true );
            Toast.makeText(this, "Purchase done. you are now a premium member.", Toast.LENGTH_SHORT).show();
        }
    }

    private Boolean getBoolFromPref(Context context, String prefName, String constantName) {
        SharedPreferences pref = context.getSharedPreferences(prefName, 0); // 0 - for private mode

        return pref.getBoolean(constantName, false);

    }

    private void setBoolInPref(Context context,String prefName, String constantName, Boolean val) {
        SharedPreferences pref = context.getSharedPreferences(prefName, 0); // 0 - for private mode

        SharedPreferences.Editor editor = pref.edit();
        editor.putBoolean(constantName, val);
        //editor.commit();
        editor.apply();
        // Update 2015: Android recommends the use of apply() now over commit(),
        // because apply() operates on a background thread instead of storing the persistent data immediately,
        // and possible blocking the main thread.
    }
}

Basically what happens when pressing the upgradeButton is that the app crashes. I cannot figure out what I am doing wrong. I am using the correct SKU, and the app has been live on Play Store for over a year now.

This is the error log I get when pressing the upgradeButton:

I/zygote: Do partial code cache collection, code=124KB, data=72KB
    After code cache collection, code=124KB, data=72KB
    Increasing code cache capacity to 512KB
D/EGL_emulation: eglMakeCurrent: 0xdb928540: ver 3 0 (tinfo 0xdb92bbd0)
D/AndroidRuntime: Shutting down VM
E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.pinloop.testproj, PID: 13352
    java.lang.IllegalArgumentException: SKU cannot be null.
        at com.android.billingclient.api.BillingFlowParams$Builder.build(com.android.billingclient:billing@@3.0.0:23)
        at com.pinloop.testproj.UpgradeActivity.upgradeAction(UpgradeActivity.java:130)
        at com.pinloop.testproj.UpgradeActivity.access$000(UpgradeActivity.java:31)
        at com.pinloop.testproj.UpgradeActivity$1.onClick(UpgradeActivity.java:56)
        at android.view.View.performClick(View.java:6294)
        at android.view.View$PerformClick.run(View.java:24770)
        at android.os.Handler.handleCallback(Handler.java:790)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6494)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)

Not sure what to look at here, but I can see that java.lang.IllegalArgumentException: SKU cannot be null., but sku has already been declared: private String sku = "com.pinloop.testproj.pro";. Any ideas?

标签: javaandroidin-app-purchasein-app-billing

解决方案


这是更新代码

import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClientStateListener;
import com.android.billingclient.api.BillingFlowParams;
import com.android.billingclient.api.BillingResult;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.android.billingclient.api.SkuDetails;
import com.android.billingclient.api.SkuDetailsParams;
import com.android.billingclient.api.SkuDetailsResponseListener;
import com.aumhum.aumhum.R;

import java.util.ArrayList;
import java.util.List;

public class UpgradeActivity extends AppCompatActivity implements PurchasesUpdatedListener {

    private Button upgradeButton;
    private TextView restoreButton;

    private BillingClient billingClient;
    private final List<String> skuList = new ArrayList();

    private final String sku = "com.pinloop.testproj.pro";

    private SkuDetails mSkuDetails;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_upgrade);

        upgradeButton = (Button) findViewById(R.id.upgradeButton);

        upgradeButton.setEnabled(false);

        upgradeButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                upgradeAction();

            }
        });

        // Check In App Purchase

        skuList.add(sku);

        setButtonStatus();

    }

    private void setButtonStatus(){

        Boolean pro = getBoolFromPref(this,"myPref", sku);
        if (pro)
        {
            Toast.makeText(this, "you are a premium user", Toast.LENGTH_SHORT).show();
            //upgradeButton.setVisibility(View.INVISIBLE);
            upgradeButton.setText(R.string.you_are_premium);

        }
        else
        {
            Toast.makeText(this, "not pro", Toast.LENGTH_SHORT).show();
            setupBillingClient();
        }
    }


    private void upgradeAction() {

        if (mSkuDetails==null){
            Toast.makeText(this,"Please wait while we get details",Toast.LENGTH_SHORT).show();
            return;
        }

        BillingFlowParams billingFlowParams = BillingFlowParams
                .newBuilder()
                .setSkuDetails(mSkuDetails)
                .build();
        billingClient.launchBillingFlow(UpgradeActivity.this, billingFlowParams);
    }


    // In App Handler:

    private void setupBillingClient() {
        billingClient = BillingClient.newBuilder(this).enablePendingPurchases().setListener(this).build();
        billingClient.startConnection(new BillingClientStateListener(){

            @Override
            public void onBillingSetupFinished(BillingResult billingResult) {
                if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                    // The BillingClient is setup successfully
                    loadAllSKUs();
                }
            }

            @Override
            public void onBillingServiceDisconnected() {
                // Try to restart the connection on the next request to
                // Google Play by calling the startConnection() method.
            }
        });
    }

    private void loadAllSKUs() {
        Toast.makeText(this, "loadAllSKUs", Toast.LENGTH_SHORT).show();

        if (billingClient.isReady())
        {
            Toast.makeText(this, "billingclient ready", Toast.LENGTH_SHORT).show();
            SkuDetailsParams params = SkuDetailsParams.newBuilder()
                    .setSkusList(skuList)
                    .setType(BillingClient.SkuType.INAPP)
                    .build();

            billingClient.querySkuDetailsAsync(params, new SkuDetailsResponseListener() {
                @Override
                public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
                    Toast.makeText(UpgradeActivity.this, "inside query" + billingResult.getResponseCode(), Toast.LENGTH_SHORT).show();
                    if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
                            && !skuDetailsList.isEmpty())
                    {
                        for (SkuDetails skuDetails : skuDetailsList) {
                            Toast.makeText(UpgradeActivity.this, "" + skuDetails.getSku(), Toast.LENGTH_SHORT).show();
                            if (skuDetails.getSku().equals(sku)) {
                                mSkuDetails = skuDetails;
                                upgradeButton.setEnabled(true);
                            }
                        }
                    }
                }
            });
        }
        else
            Toast.makeText(this, "billingclient not ready", Toast.LENGTH_SHORT).show();

    }

    @Override
    public void onPurchasesUpdated(BillingResult billingResult, @Nullable List<Purchase> purchases) {
        int responseCode = billingResult.getResponseCode();
        if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
                && purchases != null) {
            for (Purchase purchase : purchases) {
                handlePurchase(purchase);
            }
        }
        else
        if (responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
            // Handle an error caused by a user cancelling the purchase flow.
            //Log.d(TAG, "User Canceled" + responseCode);
        }
        else if (responseCode == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
            ///mSharedPreferences.edit().putBoolean(getResources().getString(R.string.pref_remove_ads_key), true).commit();
            ///setAdFree(true);
            setBoolInPref(this,"myPref",sku, true );
        }
        else {
            //Log.d(TAG, "Other code" + responseCode);
            // Handle any other error codes.
        }
    }

    private void handlePurchase(Purchase purchase) {
        if (purchase.getSku().equals(sku)) {
            ///mSharedPreferences.edit().putBoolean(getResources().getString(R.string.pref_remove_ads_key), true).commit();
            ///setAdFree(true);
            setBoolInPref(this,"myPref",sku, true );
            Toast.makeText(this, "Purchase done. you are now a premium member.", Toast.LENGTH_SHORT).show();
        }
    }

    private Boolean getBoolFromPref(Context context, String prefName, String constantName) {
        SharedPreferences pref = context.getSharedPreferences(prefName, 0); // 0 - for private mode

        return pref.getBoolean(constantName, false);

    }

    private void setBoolInPref(Context context,String prefName, String constantName, Boolean val) {
        SharedPreferences pref = context.getSharedPreferences(prefName, 0); // 0 - for private mode

        SharedPreferences.Editor editor = pref.edit();
        editor.putBoolean(constantName, val);
        //editor.commit();
        editor.apply();
        // Update 2015: Android recommends the use of apply() now over commit(),
        // because apply() operates on a background thread instead of storing the persistent data immediately,
        // and possible blocking the main thread.
    }
}

3个问题

  1. 您默认启用了升级按钮,并且在 upgradeAction 方法中没有空检查
  2. 您在 java 中通过 == 而不是 .equals 比较字符串
  3. 您正在设置升级 onclick 监听器 2 次

推荐阅读