You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android中Google Play Billing异步加载引发数组越界异常的解决方法

问题描述

我为Android应用编写了处理Google Play Billing Library的BillingManager类,代码如下:

public class BillingManager {

    private List<ProductDetails> productDetailsList;

    private PurchasesUpdatedListener purchasesUpdatedListener = (billingResult, purchases) -> {
        // To be implemented in a later section.
    };

    private BillingClient billingClient = BillingClient.newBuilder(MyApplication.getAppContext())
            .setListener(purchasesUpdatedListener)
            .enablePendingPurchases(PendingPurchasesParams.newBuilder().enableOneTimeProducts().build())
            .build();

    public void establishConnection() {
        billingClient.startConnection(new BillingClientStateListener() {
            @Override
            public void onBillingSetupFinished(BillingResult billingResult) {
                if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                    Log.i("BillingManager", "Connected to Google Play.");
                    getProductDetails();
                }
            }
            @Override
            public void onBillingServiceDisconnected() {
                Log.i("BillingManager", "Disconnected from Google Play.");
                establishConnection();
            }
        });
    }

    private void getProductDetails() {
        QueryProductDetailsParams queryProductDetailsParams =
                QueryProductDetailsParams.newBuilder()
                        .setProductList(List.of(
                                QueryProductDetailsParams.Product.newBuilder()
                                        .setProductId("full_version")
                                        .setProductType(BillingClient.ProductType.INAPP)
                                        .build()))
                        .build();

        billingClient.queryProductDetailsAsync(
                queryProductDetailsParams,
                (billingResult, productDetailsList) -> {
                    this.productDetailsList = productDetailsList;
                }
        );

    }

    public List<ProductDetails> getProductDetailsList() {
        return productDetailsList;
    }

    public void launchPurchaseFlow(Activity activity, ProductDetails productDetails) {
        List<BillingFlowParams.ProductDetailsParams> productDetailsParamsList =
                List.of(
                        BillingFlowParams.ProductDetailsParams.newBuilder()
                                // retrieve a value for "productDetails" by calling queryProductDetailsAsync()
                                .setProductDetails(productDetails)
                                .build()
                );

        BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
                .setProductDetailsParamsList(productDetailsParamsList)
                .build();

    // Launch the billing flow
        BillingResult billingResult = billingClient.launchBillingFlow(activity, billingFlowParams);
    }
}

在DialogFragment中使用该类时,尝试用获取到的产品价格设置按钮文本触发了IndexOutOfBoundsException,Fragment代码如下:

BillingManager billingManager = new BillingManager();
billingManager.establishConnection();

MaterialButton button = view.findViewById(R.id.button);

// 此代码会引发数组越界异常
button.setText(billingManager.getProductDetailsList().get(0).getOneTimePurchaseOfferDetails().getFormattedPrice());

button.setOnClickListener(v -> {
    billingManager.launchPurchaseFlow(getActivity(), billingManager.getProductDetailsList().get(0));
});

问题根源在于establishConnection和getProductDetails都是异步操作,Fragment启动时这些操作尚未完成,导致productDetailsList为空或未初始化,需要确保仅在Google Play Billing连接建立且产品详情成功获取后,再更新按钮文本。

解决方案

核心思路是通过回调机制监听产品详情加载完成事件,避免在异步操作未完成时直接访问空列表。

步骤1:给BillingManager添加回调接口

在BillingManager中定义回调接口,用于通知外部组件产品详情的加载状态:

public class BillingManager {
    // 新增回调接口,通知产品详情加载结果
    public interface OnProductDetailsLoadedListener {
        void onProductDetailsLoaded(List<ProductDetails> productDetailsList);
        void onProductDetailsLoadFailed(BillingResult billingResult);
    }

    private List<ProductDetails> productDetailsList;
    private OnProductDetailsLoadedListener loadedListener; // 回调实例

    // 新增设置回调的方法
    public void setOnProductDetailsLoadedListener(OnProductDetailsLoadedListener listener) {
        this.loadedListener = listener;
    }

    // ... 原有代码保持不变

    private void getProductDetails() {
        QueryProductDetailsParams queryProductDetailsParams =
                QueryProductDetailsParams.newBuilder()
                        .setProductList(List.of(
                                QueryProductDetailsParams.Product.newBuilder()
                                        .setProductId("full_version")
                                        .setProductType(BillingClient.ProductType.INAPP)
                                        .build()))
                        .build();

        billingClient.queryProductDetailsAsync(
                queryProductDetailsParams,
                (billingResult, productDetailsList) -> {
                    this.productDetailsList = productDetailsList;
                    // 加载完成后回调通知外部
                    if (loadedListener != null) {
                        if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && !productDetailsList.isEmpty()) {
                            loadedListener.onProductDetailsLoaded(productDetailsList);
                        } else {
                            loadedListener.onProductDetailsLoadFailed(billingResult);
                        }
                    }
                }
        );
    }

    // ... 原有代码保持不变
}

步骤2:在Fragment中实现回调并更新UI

修改DialogFragment代码,设置回调监听,仅在产品详情加载完成后更新按钮文本:

public class YourDialogFragment extends DialogFragment {
    private BillingManager billingManager;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.your_fragment_layout, container, false);
        MaterialButton button = view.findViewById(R.id.button);

        // 初始化按钮状态
        button.setEnabled(false);
        button.setText("加载中...");

        billingManager = new BillingManager();
        // 设置产品详情加载回调
        billingManager.setOnProductDetailsLoadedListener(new BillingManager.OnProductDetailsLoadedListener() {
            @Override
            public void onProductDetailsLoaded(List<ProductDetails> productDetailsList) {
                // 确保在主线程更新UI
                requireActivity().runOnUiThread(() -> {
                    ProductDetails details = productDetailsList.get(0);
                    button.setText(details.getOneTimePurchaseOfferDetails().getFormattedPrice());
                    button.setEnabled(true);
                    // 设置点击事件
                    button.setOnClickListener(v -> {
                        billingManager.launchPurchaseFlow(getActivity(), details);
                    });
                });
            }

            @Override
            public void onProductDetailsLoadFailed(BillingResult billingResult) {
                requireActivity().runOnUiThread(() -> {
                    button.setText("加载失败");
                    button.setEnabled(false);
                    Log.e("BillingError", "产品详情加载失败: " + billingResult.getDebugMessage());
                });
            }
        });

        // 建立Billing连接
        billingManager.establishConnection();

        return view;
    }

    // 移除回调,避免内存泄漏
    @Override
    public void onDestroyView() {
        super.onDestroyView();
        if (billingManager != null) {
            billingManager.setOnProductDetailsLoadedListener(null);
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 22:17:06