Google InApp Billing:onSkuDetailsResponse异步回调导致ListView绑定skuDetailsList数据后显示为空的解决咨询
解决Google Billing库异步加载订阅数据到ListView的问题
你遇到的核心问题确实是异步回调的时序问题:ListView的适配器在数据还没从Google Play返回时就已经设置好了,之后数据更新到subscribeItemDisplay但适配器没有收到通知,所以列表一直是空的。咱们一步步来修正这个流程:
第一步:提前初始化适配器,绑定空数据源
在onCreateView里先把适配器和ListView绑定,用空的subscribeItemDisplay,这样ListView有了适配器的引用,后续数据更新时能直接通知它刷新:
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Inflate layout View view = inflater.inflate(R.layout.subscribe_fragment, container, false); subscriptionsListView = view.findViewById(R.id.subscriptionsView); // 提前初始化适配器并绑定到ListView arrayAdapter = new ArrayAdapter<String>(getActivity(), R.layout.subscription_items_list, subscribeItemDisplay); subscriptionsListView.setAdapter(arrayAdapter); loadInAppProductIDS(); return view; }
第二步:在异步回调中更新数据后,通知适配器刷新
当onSkuDetailsResponse成功拿到SKU数据并更新subscribeItemDisplay后,必须调用arrayAdapter.notifyDataSetChanged(),让ListView知道数据源已经更新,需要重新渲染内容。另外要确保这个操作在主线程执行(BillingClient的回调通常在主线程,但保险起见可以加上线程判断):
public void querySkuDetails() { Log.i(TAG, "querySkuDetails"); SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder(); params.setSkusList(LIST_OF_SKUS).setType(BillingClient.SkuType.SUBS); billingClient.querySkuDetailsAsync(params.build(), new SkuDetailsResponseListener() { @Override public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) { if (billingResult == null) { return; } int responseCode = billingResult.getResponseCode(); switch (responseCode) { case BillingClient.BillingResponseCode.OK: if (skuDetailsList != null && !skuDetailsList.isEmpty()) { subscribeItemDisplay.clear(); for (SkuDetails p : skuDetailsList) { subscribeItemDisplay.add("Product Name - "+p.getOriginalPrice()+": "+p.getSubscriptionPeriod()+": "+p.getFreeTrialPeriod()); } // 通知适配器数据已更新,刷新ListView getActivity().runOnUiThread(() -> { if (arrayAdapter != null) { arrayAdapter.notifyDataSetChanged(); } }); } break; // 必须添加break,避免逻辑流入default分支 default: Log.w(TAG, "SKU查询失败:" + billingResult.getDebugMessage()); break; } } }); }
第三步:简化loadInAppProductIDS的逻辑
原来的代码里在子线程中启动连接后立刻post设置适配器,这完全没必要,现在适配器已经在onCreateView里初始化好了,这里只需要处理SKU列表的初始化和启动连接即可:
public void loadInAppProductIDS() { new Thread(new Runnable() { @Override public void run() { LIST_OF_SKUS = Collections.unmodifiableList(myProductIDs); // 启动Billing连接,后续的查询和数据更新会触发适配器刷新 startBillingServiceConnection(); } }).start(); }
关键逻辑梳理
- 先绑定适配器:让ListView和适配器建立关联,即使数据源是空的,ListView也知道该用哪个适配器来渲染。
- 异步回调更新数据后通知适配器:当Google Play返回SKU数据时,更新数据源,然后调用
notifyDataSetChanged()触发ListView重新加载数据——这和你用测试数据时的逻辑一致,测试数据是同步添加的,适配器能立刻感知,而异步数据需要手动触发刷新。 - 修正switch分支的break:原来的代码里
case OK后没有break,会导致逻辑走到default,虽然这里不影响功能,但养成良好的编码习惯能避免后续潜在问题。
这样调整后,当异步数据回来时,ListView就能自动刷新显示订阅内容了。
内容的提问来源于stack exchange,提问作者Kamal
相关产品推荐
相关产品推荐

