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
相关产品推荐
相关产品推荐

