Googlein-app计费,一声吐司打破一切
Google in-app billing, a toast breaks everything
我什至不知道这个问题应该起什么标题。
所以,我正在开发一个库,以使 Google-Billing
尽可能容易地集成。
一切都是在我将库升级到 Google's Billing v4
之后开始的,它将 Purchase#getSku
替换为 Purchase#getSkus()
。
这是迁移到billing v4
之前和之后:
之前(v3 库):
private void processPurchases(List<Purchase> allPurchases, boolean purchasedProductsFetched) {
if (!allPurchases.isEmpty()) {
List<PurchaseInfo> signatureValidPurchases = new ArrayList<>();
//create a list with signature valid purchases
List<Purchase> validPurchases = allPurchases.stream().filter(this::isPurchaseSignatureValid).collect(Collectors.toList());
for (Purchase purchase : validPurchases) {
Optional<SkuInfo> skuInfo = fetchedSkuInfoList.stream().filter(it -> it.getSkuId().equals(purchase.getSku())).findFirst();
if (skuInfo.isPresent()) {
SkuDetails skuDetails = skuInfo.get().getSkuDetails();
PurchaseInfo purchaseInfo = new PurchaseInfo(generateSkuInfo(skuDetails), purchase);
signatureValidPurchases.add(purchaseInfo);
}
}
if (purchasedProductsFetched) {
fetchedPurchasedProducts = true;
billingEventListener.onPurchasedProductsFetched(signatureValidPurchases);
} else {
billingEventListener.onProductsPurchased(signatureValidPurchases);
}
purchasedProductsList.addAll(signatureValidPurchases);
for (PurchaseInfo purchaseInfo : signatureValidPurchases) {
consume(purchaseInfo);
if (shouldAutoAcknowledge) {
boolean wasConsumedBefore = purchaseInfo.getSkuProductType() == SkuProductType.CONSUMABLE;
if (!wasConsumedBefore) {
acknowledgePurchase(purchaseInfo);
}
}
}
}
}
之后(v4 库):
private void processPurchases(List<Purchase> allPurchases, boolean purchasedProductsFetched) {
if (!allPurchases.isEmpty()) {
List<PurchaseInfo> signatureValidPurchases = new ArrayList<>();
//create a list with signature valid purchases
List<Purchase> validPurchases = allPurchases.stream().filter(this::isPurchaseSignatureValid).collect(Collectors.toList());
for (Purchase purchase : validPurchases) {
//query all SKUs as a list
List<String> purchasesSkus = purchase.getSkus();
//loop through all SKUs and progress for each SKU individually
for (int i = 0; i < purchasesSkus.size(); i++) {
String purchasesSku = purchasesSkus.get(i);
Optional<SkuInfo> skuInfo = fetchedSkuInfoList.stream().filter(it -> it.getSkuId().equals(purchasesSku)).findFirst();
if (skuInfo.isPresent()) {
SkuDetails skuDetails = skuInfo.get().getSkuDetails();
PurchaseInfo purchaseInfo = new PurchaseInfo(generateSkuInfo(skuDetails), purchase);
signatureValidPurchases.add(purchaseInfo);
}
}
}
if (purchasedProductsFetched) {
fetchedPurchasedProducts = true;
billingEventListener.onPurchasedProductsFetched(signatureValidPurchases);
} else {
billingEventListener.onProductsPurchased(signatureValidPurchases);
}
purchasedProductsList.addAll(signatureValidPurchases);
for (PurchaseInfo purchaseInfo : signatureValidPurchases) {
consume(purchaseInfo);
if (shouldAutoAcknowledge) {
boolean wasConsumedBefore = purchaseInfo.getSkuProductType() == SkuProductType.CONSUMABLE;
if (!wasConsumedBefore) {
acknowledgePurchase(purchaseInfo);
}
}
}
}
}
注意 getSku()
和 getSkus()
的变化。
到目前为止一切顺利。继续解决问题...
在我的图书馆中,我有一个名为 onProductsFetched
的 listener
,它将提供一个 List<SkuInfo>
和获取的 SKU 详细信息。
示例:
billingConnector.setBillingEventListener(new BillingEventListener() {
@Override
public void onProductsFetched(@NonNull List<SkuInfo> skuDetailsList) {
String sku;
for (SkuInfo skuInfo : skuDetailsList) {
sku = skuInfo.getSkuId();
if (sku.equalsIgnoreCase(getString(R.string.billing_test_purchase))) {
Log.d("IapConnector", "SKU FOUND: " + sku);
Toast.makeText(SettingsActivity.this, "Fetched: " + sku, Toast.LENGTH_SHORT).show();
}
}
}
});
问题说明:
当使用上面 Before (v3 library)
中提供的代码时,一切都按预期工作,触发侦听器并显示 toast。切换到 After (v4 library)
toast 后,一切都崩溃了。日志已在 logcat
中注册,但 toast 未显示。更重要的是,如果我在 toast 之后调用另一个日志,它也不会被注册,实际上在 toast 之后什么都不会被调用,甚至 finish()
。但是等等,还有更多,如果我根本不祝酒,一切都会很好。如果我将 toast
替换为 snackbar
,则会显示小吃栏。
什么会导致这个问题?有什么想法吗?
更新:
如果我延迟调用 Toast
,一切都会按预期进行。
final Handler handler = new Handler(Looper.getMainLooper());
String finalSku = sku;
handler.postDelayed(new Runnable() {
@Override
public void run() {
Toast.makeText(SettingsActivity.this, "Fetched: " + finalSku, Toast.LENGTH_SHORT).show();
}
}, 100);
更新2:
Logcat。注意 D/IapConnector: SKU FOUND: test_purchase7
被调用,它是 Toast
之前的 log
2021-06-07 11:11:37.633 31680-31680/games.moisoni.evfp I/ViewRootImpl@ef10be4[MainActivity]: ViewPostIme pointer 0
2021-06-07 11:11:37.710 31680-31680/games.moisoni.evfp I/ViewRootImpl@ef10be4[MainActivity]: ViewPostIme pointer 1
2021-06-07 11:11:37.747 31680-32423/games.moisoni.evfp V/FA: Recording user engagement, ms: 11555
2021-06-07 11:11:37.752 31680-32423/games.moisoni.evfp V/FA: Connecting to remote service
2021-06-07 11:11:37.755 31680-32423/games.moisoni.evfp V/FA: Activity paused, time: 53257103
2021-06-07 11:11:37.769 31680-32423/games.moisoni.evfp V/FA: Connection attempt already in progress
2021-06-07 11:11:37.770 31680-32417/games.moisoni.evfp V/FA: onActivityCreated
2021-06-07 11:11:37.772 31680-31680/games.moisoni.evfp I/DecorView: [INFO] isPopOver=false, config=true
2021-06-07 11:11:37.772 31680-31680/games.moisoni.evfp I/DecorView: updateCaptionType >> DecorView@25a7196[], isFloating=false, isApplication=true, hasWindowDecorCaption=false, hasWindowControllerCallback=true
2021-06-07 11:11:37.772 31680-31680/games.moisoni.evfp D/DecorView: setCaptionType = 0, this = DecorView@25a7196[]
2021-06-07 11:11:37.782 31680-31680/games.moisoni.evfp D/ScrollView: initGoToTop
2021-06-07 11:11:37.830 31680-31680/games.moisoni.evfp D/IapConnector: Billing service: connecting...
2021-06-07 11:11:37.853 31680-32423/games.moisoni.evfp V/FA: Activity resumed, time: 53257213
2021-06-07 11:11:37.857 31680-31680/games.moisoni.evfp D/InputTransport: Input channel constructed: 'f0be3be', fd=218
2021-06-07 11:11:37.859 31680-31680/games.moisoni.evfp I/ViewRootImpl@d0eac04[SettingsActivity]: setView = com.android.internal.policy.DecorView@25a7196 TM=true
2021-06-07 11:11:37.870 31680-32423/games.moisoni.evfp V/FA: Connection attempt already in progress
2021-06-07 11:11:37.872 31680-32423/games.moisoni.evfp V/FA: Connection attempt already in progress
2021-06-07 11:11:37.879 31680-31680/games.moisoni.evfp D/ViewRootImpl@d0eac04[SettingsActivity]: controlInsetsForCompatibility: hideByFlags=0x3, showByFlags=0x0, flags=0x89810500, sysUiVis=0x1706, matchParent=true, nonAttachedAppWindow=true
2021-06-07 11:11:37.879 31680-31680/games.moisoni.evfp D/InsetsSourceConsumer: setRequestedVisible: visible=false, type=1, host=games.moisoni.evfp/games.moisoni.evfp.SettingsActivity, from=android.view.InsetsSourceConsumer.hide:236 android.view.InsetsController.collectSourceControls:1172 android.view.InsetsController.controlAnimationUnchecked:1049 android.view.InsetsController.applyAnimation:1417 android.view.InsetsController.hide:984 android.view.InsetsController.hide:967 android.view.ViewRootImpl.controlInsetsForCompatibility:2852 android.view.ViewRootImpl.performTraversals:3314 android.view.ViewRootImpl.doTraversal:2618 android.view.ViewRootImpl$TraversalRunnable.run:9965
2021-06-07 11:11:37.879 31680-31680/games.moisoni.evfp D/InsetsSourceConsumer: setRequestedVisible: visible=false, type=0, host=games.moisoni.evfp/games.moisoni.evfp.SettingsActivity, from=android.view.InsetsSourceConsumer.hide:236 android.view.InsetsController.collectSourceControls:1172 android.view.InsetsController.controlAnimationUnchecked:1049 android.view.InsetsController.applyAnimation:1417 android.view.InsetsController.hide:984 android.view.InsetsController.hide:967 android.view.ViewRootImpl.controlInsetsForCompatibility:2852 android.view.ViewRootImpl.performTraversals:3314 android.view.ViewRootImpl.doTraversal:2618 android.view.ViewRootImpl$TraversalRunnable.run:9965
2021-06-07 11:11:37.893 31680-31680/games.moisoni.evfp I/SurfaceControl: assignNativeObject: nativeObject = 0 Surface(name=null)/@0xa900b0f / android.view.SurfaceControl.readFromParcel:1115 android.view.IWindowSession$Stub$Proxy.relayout:1820 android.view.ViewRootImpl.relayoutWindow:9005 android.view.ViewRootImpl.performTraversals:3360 android.view.ViewRootImpl.doTraversal:2618 android.view.ViewRootImpl$TraversalRunnable.run:9965 android.view.Choreographer$CallbackRecord.run:1010 android.view.Choreographer.doCallbacks:809 android.view.Choreographer.doFrame:744 android.view.Choreographer$FrameDisplayEventReceiver.run:995
2021-06-07 11:11:37.894 31680-31680/games.moisoni.evfp I/ViewRootImpl@d0eac04[SettingsActivity]: Relayout returned: old=(0,0,1440,3040) new=(0,144,1440,3040) req=(1440,3040)0 dur=10 res=0x7 s={true 535365349344} ch=true fn=-1
2021-06-07 11:11:37.902 31680-31680/games.moisoni.evfp D/ScrollView: onsize change changed
2021-06-07 11:11:37.902 31680-31680/games.moisoni.evfp I/ViewRootImpl@d0eac04[SettingsActivity]: [DP] dp(1) 1 android.view.ViewRootImpl.reportNextDraw:10951 android.view.ViewRootImpl.performTraversals:3845 android.view.ViewRootImpl.doTraversal:2618
2021-06-07 11:11:37.902 31680-31680/games.moisoni.evfp I/ViewRootImpl@d0eac04[SettingsActivity]: [DP] pd() Asnyc report
2021-06-07 11:11:37.922 31680-31680/games.moisoni.evfp I/ViewRootImpl@d0eac04[SettingsActivity]: [DP] pdf(0) 1 android.view.ViewRootImpl.lambda$performDraw$ViewRootImpl:4668 android.view.-$$Lambda$ViewRootImpl$DJd0VUYJgsebcnSohO6h8zc_ONI.run:6 android.os.Handler.handleCallback:938
2021-06-07 11:11:37.922 31680-31680/games.moisoni.evfp I/ViewRootImpl@d0eac04[SettingsActivity]: [DP] rdf()
2021-06-07 11:11:37.927 31680-31680/games.moisoni.evfp I/ViewRootImpl@ef10be4[MainActivity]: MSG_WINDOW_FOCUS_CHANGED 0 1
2021-06-07 11:11:37.928 31680-32758/games.moisoni.evfp D/IapConnector: Billing service: connected
2021-06-07 11:11:37.933 31680-32423/games.moisoni.evfp D/FA: Connected to remote service
2021-06-07 11:11:37.935 31680-32423/games.moisoni.evfp V/FA: Processing queued up service tasks: 4
2021-06-07 11:11:37.938 31680-32759/games.moisoni.evfp D/IapConnector: Query SKU Details: data found
2021-06-07 11:11:37.938 31680-32758/games.moisoni.evfp D/IapConnector: Subscriptions support check: success
2021-06-07 11:11:37.939 31680-32759/games.moisoni.evfp D/IapConnector: SKU FOUND: test_purchase7
2021-06-07 11:11:37.940 31680-32759/games.moisoni.evfp D/CompatibilityChangeReporter: Compat change id reported: 147798919; UID 10745; state: ENABLED
2021-06-07 11:11:37.948 31680-31680/games.moisoni.evfp D/SurfaceControl: hide : mNativeObject = 531070505712 - sc.mNativeObject = 530532840928 - Surface(name=Surface(name=a8e3812 NavigationBar0)/@0x38b32cd - animation-leash)/@0x2a3e45d
2021-06-07 11:11:37.948 31680-31680/games.moisoni.evfp D/SurfaceControl: nativeSetFlags Done : Surface(name=Surface(name=a8e3812 NavigationBar0)/@0x38b32cd - animation-leash)/@0x2a3e45d
2021-06-07 11:11:37.949 31680-31680/games.moisoni.evfp D/SurfaceControl: hide : mNativeObject = 531070497088 - sc.mNativeObject = 530532838464 - Surface(name=Surface(name=e511e35 StatusBar)/@0xbf071b - animation-leash)/@0xc4ac1d2
2021-06-07 11:11:37.949 31680-31680/games.moisoni.evfp D/SurfaceControl: nativeSetFlags Done : Surface(name=Surface(name=e511e35 StatusBar)/@0xbf071b - animation-leash)/@0xc4ac1d2
2021-06-07 11:11:37.951 31680-31680/games.moisoni.evfp I/SurfaceControl: release : mNativeObject = 530532781344 - Surface(name=Surface(name=37630c6 InputMethod)/@0xd11e623 - animation-leash)/@0xe000b05 / android.view.-$$Lambda$Rl1VZmNJ0VZDLK0BAbaVGis0rrA.accept:2 android.view.InsetsSourceControl.release:170 android.view.InsetsSourceConsumer.setControl:202 android.view.ImeInsetsSourceConsumer.setControl:154
2021-06-07 11:11:37.951 31680-31680/games.moisoni.evfp I/SurfaceControl: nativeRelease nativeObject s[530532781344]
2021-06-07 11:11:37.951 31680-31680/games.moisoni.evfp I/SurfaceControl: nativeRelease nativeObject e[530532781344]
2021-06-07 11:11:37.951 31680-31680/games.moisoni.evfp I/SurfaceControl: release : mNativeObject = 530532786048 - Surface(name=Surface(name=a8e3812 NavigationBar0)/@0x38b32cd - animation-leash)/@0xd4eb2a3 / android.view.-$$Lambda$Rl1VZmNJ0VZDLK0BAbaVGis0rrA.accept:2 android.view.InsetsSourceControl.release:170 android.view.InsetsSourceConsumer.setControl:202 android.view.InsetsController.onControlsChanged:833
2021-06-07 11:11:37.951 31680-31680/games.moisoni.evfp I/SurfaceControl: nativeRelease nativeObject s[530532786048]
2021-06-07 11:11:37.951 31680-31680/games.moisoni.evfp I/SurfaceControl: nativeRelease nativeObject e[530532786048]
2021-06-07 11:11:37.951 31680-31680/games.moisoni.evfp I/SurfaceControl: release : mNativeObject = 530532732064 - Surface(name=Surface(name=e511e35 StatusBar)/@0xbf071b - animation-leash)/@0x201e9a0 / android.view.-$$Lambda$Rl1VZmNJ0VZDLK0BAbaVGis0rrA.accept:2 android.view.InsetsSourceControl.release:170 android.view.InsetsSourceConsumer.setControl:202 android.view.InsetsController.onControlsChanged:833
2021-06-07 11:11:37.951 31680-31680/games.moisoni.evfp I/SurfaceControl: nativeRelease nativeObject s[530532732064]
2021-06-07 11:11:37.951 31680-31680/games.moisoni.evfp I/SurfaceControl: nativeRelease nativeObject e[530532732064]
2021-06-07 11:11:37.955 31680-31680/games.moisoni.evfp I/ViewRootImpl@d0eac04[SettingsActivity]: MSG_WINDOW_FOCUS_CHANGED 1 1
2021-06-07 11:11:37.955 31680-31680/games.moisoni.evfp D/InputMethodManager: prepareNavigationBarInfo() DecorView@25a7196[SettingsActivity]
2021-06-07 11:11:37.955 31680-31680/games.moisoni.evfp D/InputMethodManager: getNavigationBarColor() -16711423
2021-06-07 11:11:37.957 31680-31680/games.moisoni.evfp D/InputMethodManager: prepareNavigationBarInfo() DecorView@25a7196[SettingsActivity]
2021-06-07 11:11:37.957 31680-31680/games.moisoni.evfp D/InputMethodManager: getNavigationBarColor() -16711423
2021-06-07 11:11:37.957 31680-31680/games.moisoni.evfp V/InputMethodManager: Starting input: tba=games.moisoni.evfp ic=null mNaviBarColor -16711423 mIsGetNaviBarColorSuccess true , NavVisible : false , NavTrans : false
2021-06-07 11:11:37.957 31680-31680/games.moisoni.evfp D/InputMethodManager: startInputInner - Id : 0
2021-06-07 11:11:37.957 31680-31680/games.moisoni.evfp I/InputMethodManager: startInputInner - mService.startInputOrWindowGainedFocus
2021-06-07 11:11:37.959 31680-31680/games.moisoni.evfp D/InputTransport: Input channel constructed: 'ClientS', fd=221
2021-06-07 11:11:37.959 31680-31680/games.moisoni.evfp D/InputTransport: Input channel destroyed: 'ClientS', fd=111
2021-06-07 11:11:37.998 31680-31680/games.moisoni.evfp I/SurfaceControl: release : mNativeObject = 530532838240 - Surface(name=Surface(name=37630c6 InputMethod)/@0xd11e623 - animation-leash)/@0x46f291e / android.view.-$$Lambda$Rl1VZmNJ0VZDLK0BAbaVGis0rrA.accept:2 android.view.InsetsSourceControl.release:170 android.view.InsetsSourceConsumer.setControl:202 android.view.ImeInsetsSourceConsumer.setControl:154
2021-06-07 11:11:37.998 31680-31680/games.moisoni.evfp I/SurfaceControl: nativeRelease nativeObject s[530532838240]
2021-06-07 11:11:37.998 31680-31680/games.moisoni.evfp I/SurfaceControl: nativeRelease nativeObject e[530532838240]
2021-06-07 11:11:37.999 31680-31680/games.moisoni.evfp D/SurfaceControl: hide : mNativeObject = 531069881968 - sc.mNativeObject = 530532839920 - Surface(name=Surface(name=37630c6 InputMethod)/@0xd11e623 - animation-leash)/@0xd0fd5ff
2021-06-07 11:11:37.999 31680-31680/games.moisoni.evfp D/SurfaceControl: nativeSetFlags Done : Surface(name=Surface(name=37630c6 InputMethod)/@0xd11e623 - animation-leash)/@0xd0fd5ff
2021-06-07 11:11:38.001 31680-31680/games.moisoni.evfp I/SurfaceControl: release : mNativeObject = 530532840928 - Surface(name=Surface(name=a8e3812 NavigationBar0)/@0x38b32cd - animation-leash)/@0x2a3e45d / android.view.-$$Lambda$Rl1VZmNJ0VZDLK0BAbaVGis0rrA.accept:2 android.view.InsetsSourceControl.release:170 android.view.InsetsSourceConsumer.setControl:202 android.view.InsetsController.onControlsChanged:833
2021-06-07 11:11:38.001 31680-31680/games.moisoni.evfp I/SurfaceControl: nativeRelease nativeObject s[530532840928]
2021-06-07 11:11:38.001 31680-31680/games.moisoni.evfp I/SurfaceControl: nativeRelease nativeObject e[530532840928]
2021-06-07 11:11:38.001 31680-31680/games.moisoni.evfp I/SurfaceControl: release : mNativeObject = 530532838464 - Surface(name=Surface(name=e511e35 StatusBar)/@0xbf071b - animation-leash)/@0xc4ac1d2 / android.view.-$$Lambda$Rl1VZmNJ0VZDLK0BAbaVGis0rrA.accept:2 android.view.InsetsSourceControl.release:170 android.view.InsetsSourceConsumer.setControl:202 android.view.InsetsController.onControlsChanged:833
2021-06-07 11:11:38.001 31680-31680/games.moisoni.evfp I/SurfaceControl: nativeRelease nativeObject s[530532838464]
2021-06-07 11:11:38.001 31680-31680/games.moisoni.evfp I/SurfaceControl: nativeRelease nativeObject e[530532838464]
2021-06-07 11:11:38.185 31680-31680/games.moisoni.evfp I/ViewRootImpl@ef10be4[MainActivity]: stopped(true) old=false
2021-06-07 11:11:38.187 31680-31680/games.moisoni.evfp I/SurfaceControl: release : mNativeObject = 530532758608 - Surface(name=games.moisoni.evfp/games.moisoni.evfp.MainActivity$_31680)/@0xf744215 / android.view.ViewRootImpl.destroySurface:2484 android.view.ViewRootImpl.setWindowStopped:2332 android.view.WindowManagerGlobal.setStoppedState:741 android.app.Activity.performStop:8423
2021-06-07 11:11:38.187 31680-31680/games.moisoni.evfp I/SurfaceControl: nativeRelease nativeObject s[530532758608]
2021-06-07 11:11:38.187 31680-31680/games.moisoni.evfp I/SurfaceControl: nativeRelease nativeObject e[530532758608]
2021-06-07 11:11:38.202 31680-32437/games.moisoni.evfp W/libEGL: EGLNativeWindowType 0x7ca63be2a0 disconnect failed
2021-06-07 11:11:38.206 31680-31680/games.moisoni.evfp I/SurfaceControl: assignNativeObject: nativeObject = 0 Surface(name=null)/@0xf744215 / android.view.SurfaceControl.readFromParcel:1115 android.view.IWindowSession$Stub$Proxy.relayout:1810 android.view.ViewRootImpl.relayoutWindow:9005 android.view.ViewRootImpl.performTraversals:3360 android.view.ViewRootImpl.doTraversal:2618 android.view.ViewRootImpl$TraversalRunnable.run:9965 android.view.Choreographer$CallbackRecord.run:1010 android.view.Choreographer.doCallbacks:809 android.view.Choreographer.doFrame:744 android.view.Choreographer$FrameDisplayEventReceiver.run:995
2021-06-07 11:11:38.206 31680-31680/games.moisoni.evfp I/SurfaceControl: assignNativeObject: nativeObject = 0 Surface(name=null)/@0xda8bd8d / android.view.SurfaceControl.readFromParcel:1115 android.view.IWindowSession$Stub$Proxy.relayout:1820 android.view.ViewRootImpl.relayoutWindow:9005 android.view.ViewRootImpl.performTraversals:3360 android.view.ViewRootImpl.doTraversal:2618 android.view.ViewRootImpl$TraversalRunnable.run:9965 android.view.Choreographer$CallbackRecord.run:1010 android.view.Choreographer.doCallbacks:809 android.view.Choreographer.doFrame:744 android.view.Choreographer$FrameDisplayEventReceiver.run:995
2021-06-07 11:11:38.207 31680-31680/games.moisoni.evfp I/ViewRootImpl@ef10be4[MainActivity]: Relayout returned: old=(0,144,1440,3040) new=(0,144,1440,3040) req=(1440,2896)8 dur=4 res=0x5 s={false 0} ch=false fn=-1
在 V4.0 中,BillingClient 方法回调在后台线程中调用,而不是在主线程中调用。
解决方法是在 BillingClient 回调中将线程更改为 Main。
在 UI 线程中用 Toast 调用方法 processPurchases()。
@researcher answer 是一个完全有效的选项,但对我来说,解决这个问题的最佳方法是触发 main UI thread
上的 listeners
,而不是更改所有 [=] 的线程14=] 回调。
他的回答将继续被接受,因为他发现了问题。
为了在 UI 线程上调用 listeners
我创建了一个方法:
private Handler findUiHandler() {
return new Handler(Looper.getMainLooper());
}
然后像这样调用监听器:
findUiHandler().post(() -> billingEventListener.onProductsFetched(fetchedSkuInfo));
我什至不知道这个问题应该起什么标题。
所以,我正在开发一个库,以使 Google-Billing
尽可能容易地集成。
一切都是在我将库升级到 Google's Billing v4
之后开始的,它将 Purchase#getSku
替换为 Purchase#getSkus()
。
这是迁移到billing v4
之前和之后:
之前(v3 库):
private void processPurchases(List<Purchase> allPurchases, boolean purchasedProductsFetched) {
if (!allPurchases.isEmpty()) {
List<PurchaseInfo> signatureValidPurchases = new ArrayList<>();
//create a list with signature valid purchases
List<Purchase> validPurchases = allPurchases.stream().filter(this::isPurchaseSignatureValid).collect(Collectors.toList());
for (Purchase purchase : validPurchases) {
Optional<SkuInfo> skuInfo = fetchedSkuInfoList.stream().filter(it -> it.getSkuId().equals(purchase.getSku())).findFirst();
if (skuInfo.isPresent()) {
SkuDetails skuDetails = skuInfo.get().getSkuDetails();
PurchaseInfo purchaseInfo = new PurchaseInfo(generateSkuInfo(skuDetails), purchase);
signatureValidPurchases.add(purchaseInfo);
}
}
if (purchasedProductsFetched) {
fetchedPurchasedProducts = true;
billingEventListener.onPurchasedProductsFetched(signatureValidPurchases);
} else {
billingEventListener.onProductsPurchased(signatureValidPurchases);
}
purchasedProductsList.addAll(signatureValidPurchases);
for (PurchaseInfo purchaseInfo : signatureValidPurchases) {
consume(purchaseInfo);
if (shouldAutoAcknowledge) {
boolean wasConsumedBefore = purchaseInfo.getSkuProductType() == SkuProductType.CONSUMABLE;
if (!wasConsumedBefore) {
acknowledgePurchase(purchaseInfo);
}
}
}
}
}
之后(v4 库):
private void processPurchases(List<Purchase> allPurchases, boolean purchasedProductsFetched) {
if (!allPurchases.isEmpty()) {
List<PurchaseInfo> signatureValidPurchases = new ArrayList<>();
//create a list with signature valid purchases
List<Purchase> validPurchases = allPurchases.stream().filter(this::isPurchaseSignatureValid).collect(Collectors.toList());
for (Purchase purchase : validPurchases) {
//query all SKUs as a list
List<String> purchasesSkus = purchase.getSkus();
//loop through all SKUs and progress for each SKU individually
for (int i = 0; i < purchasesSkus.size(); i++) {
String purchasesSku = purchasesSkus.get(i);
Optional<SkuInfo> skuInfo = fetchedSkuInfoList.stream().filter(it -> it.getSkuId().equals(purchasesSku)).findFirst();
if (skuInfo.isPresent()) {
SkuDetails skuDetails = skuInfo.get().getSkuDetails();
PurchaseInfo purchaseInfo = new PurchaseInfo(generateSkuInfo(skuDetails), purchase);
signatureValidPurchases.add(purchaseInfo);
}
}
}
if (purchasedProductsFetched) {
fetchedPurchasedProducts = true;
billingEventListener.onPurchasedProductsFetched(signatureValidPurchases);
} else {
billingEventListener.onProductsPurchased(signatureValidPurchases);
}
purchasedProductsList.addAll(signatureValidPurchases);
for (PurchaseInfo purchaseInfo : signatureValidPurchases) {
consume(purchaseInfo);
if (shouldAutoAcknowledge) {
boolean wasConsumedBefore = purchaseInfo.getSkuProductType() == SkuProductType.CONSUMABLE;
if (!wasConsumedBefore) {
acknowledgePurchase(purchaseInfo);
}
}
}
}
}
注意 getSku()
和 getSkus()
的变化。
到目前为止一切顺利。继续解决问题...
在我的图书馆中,我有一个名为 onProductsFetched
的 listener
,它将提供一个 List<SkuInfo>
和获取的 SKU 详细信息。
示例:
billingConnector.setBillingEventListener(new BillingEventListener() {
@Override
public void onProductsFetched(@NonNull List<SkuInfo> skuDetailsList) {
String sku;
for (SkuInfo skuInfo : skuDetailsList) {
sku = skuInfo.getSkuId();
if (sku.equalsIgnoreCase(getString(R.string.billing_test_purchase))) {
Log.d("IapConnector", "SKU FOUND: " + sku);
Toast.makeText(SettingsActivity.this, "Fetched: " + sku, Toast.LENGTH_SHORT).show();
}
}
}
});
问题说明:
当使用上面 Before (v3 library)
中提供的代码时,一切都按预期工作,触发侦听器并显示 toast。切换到 After (v4 library)
toast 后,一切都崩溃了。日志已在 logcat
中注册,但 toast 未显示。更重要的是,如果我在 toast 之后调用另一个日志,它也不会被注册,实际上在 toast 之后什么都不会被调用,甚至 finish()
。但是等等,还有更多,如果我根本不祝酒,一切都会很好。如果我将 toast
替换为 snackbar
,则会显示小吃栏。
什么会导致这个问题?有什么想法吗?
更新:
如果我延迟调用 Toast
,一切都会按预期进行。
final Handler handler = new Handler(Looper.getMainLooper());
String finalSku = sku;
handler.postDelayed(new Runnable() {
@Override
public void run() {
Toast.makeText(SettingsActivity.this, "Fetched: " + finalSku, Toast.LENGTH_SHORT).show();
}
}, 100);
更新2:
Logcat。注意 D/IapConnector: SKU FOUND: test_purchase7
被调用,它是 Toast
log
2021-06-07 11:11:37.633 31680-31680/games.moisoni.evfp I/ViewRootImpl@ef10be4[MainActivity]: ViewPostIme pointer 0
2021-06-07 11:11:37.710 31680-31680/games.moisoni.evfp I/ViewRootImpl@ef10be4[MainActivity]: ViewPostIme pointer 1
2021-06-07 11:11:37.747 31680-32423/games.moisoni.evfp V/FA: Recording user engagement, ms: 11555
2021-06-07 11:11:37.752 31680-32423/games.moisoni.evfp V/FA: Connecting to remote service
2021-06-07 11:11:37.755 31680-32423/games.moisoni.evfp V/FA: Activity paused, time: 53257103
2021-06-07 11:11:37.769 31680-32423/games.moisoni.evfp V/FA: Connection attempt already in progress
2021-06-07 11:11:37.770 31680-32417/games.moisoni.evfp V/FA: onActivityCreated
2021-06-07 11:11:37.772 31680-31680/games.moisoni.evfp I/DecorView: [INFO] isPopOver=false, config=true
2021-06-07 11:11:37.772 31680-31680/games.moisoni.evfp I/DecorView: updateCaptionType >> DecorView@25a7196[], isFloating=false, isApplication=true, hasWindowDecorCaption=false, hasWindowControllerCallback=true
2021-06-07 11:11:37.772 31680-31680/games.moisoni.evfp D/DecorView: setCaptionType = 0, this = DecorView@25a7196[]
2021-06-07 11:11:37.782 31680-31680/games.moisoni.evfp D/ScrollView: initGoToTop
2021-06-07 11:11:37.830 31680-31680/games.moisoni.evfp D/IapConnector: Billing service: connecting...
2021-06-07 11:11:37.853 31680-32423/games.moisoni.evfp V/FA: Activity resumed, time: 53257213
2021-06-07 11:11:37.857 31680-31680/games.moisoni.evfp D/InputTransport: Input channel constructed: 'f0be3be', fd=218
2021-06-07 11:11:37.859 31680-31680/games.moisoni.evfp I/ViewRootImpl@d0eac04[SettingsActivity]: setView = com.android.internal.policy.DecorView@25a7196 TM=true
2021-06-07 11:11:37.870 31680-32423/games.moisoni.evfp V/FA: Connection attempt already in progress
2021-06-07 11:11:37.872 31680-32423/games.moisoni.evfp V/FA: Connection attempt already in progress
2021-06-07 11:11:37.879 31680-31680/games.moisoni.evfp D/ViewRootImpl@d0eac04[SettingsActivity]: controlInsetsForCompatibility: hideByFlags=0x3, showByFlags=0x0, flags=0x89810500, sysUiVis=0x1706, matchParent=true, nonAttachedAppWindow=true
2021-06-07 11:11:37.879 31680-31680/games.moisoni.evfp D/InsetsSourceConsumer: setRequestedVisible: visible=false, type=1, host=games.moisoni.evfp/games.moisoni.evfp.SettingsActivity, from=android.view.InsetsSourceConsumer.hide:236 android.view.InsetsController.collectSourceControls:1172 android.view.InsetsController.controlAnimationUnchecked:1049 android.view.InsetsController.applyAnimation:1417 android.view.InsetsController.hide:984 android.view.InsetsController.hide:967 android.view.ViewRootImpl.controlInsetsForCompatibility:2852 android.view.ViewRootImpl.performTraversals:3314 android.view.ViewRootImpl.doTraversal:2618 android.view.ViewRootImpl$TraversalRunnable.run:9965
2021-06-07 11:11:37.879 31680-31680/games.moisoni.evfp D/InsetsSourceConsumer: setRequestedVisible: visible=false, type=0, host=games.moisoni.evfp/games.moisoni.evfp.SettingsActivity, from=android.view.InsetsSourceConsumer.hide:236 android.view.InsetsController.collectSourceControls:1172 android.view.InsetsController.controlAnimationUnchecked:1049 android.view.InsetsController.applyAnimation:1417 android.view.InsetsController.hide:984 android.view.InsetsController.hide:967 android.view.ViewRootImpl.controlInsetsForCompatibility:2852 android.view.ViewRootImpl.performTraversals:3314 android.view.ViewRootImpl.doTraversal:2618 android.view.ViewRootImpl$TraversalRunnable.run:9965
2021-06-07 11:11:37.893 31680-31680/games.moisoni.evfp I/SurfaceControl: assignNativeObject: nativeObject = 0 Surface(name=null)/@0xa900b0f / android.view.SurfaceControl.readFromParcel:1115 android.view.IWindowSession$Stub$Proxy.relayout:1820 android.view.ViewRootImpl.relayoutWindow:9005 android.view.ViewRootImpl.performTraversals:3360 android.view.ViewRootImpl.doTraversal:2618 android.view.ViewRootImpl$TraversalRunnable.run:9965 android.view.Choreographer$CallbackRecord.run:1010 android.view.Choreographer.doCallbacks:809 android.view.Choreographer.doFrame:744 android.view.Choreographer$FrameDisplayEventReceiver.run:995
2021-06-07 11:11:37.894 31680-31680/games.moisoni.evfp I/ViewRootImpl@d0eac04[SettingsActivity]: Relayout returned: old=(0,0,1440,3040) new=(0,144,1440,3040) req=(1440,3040)0 dur=10 res=0x7 s={true 535365349344} ch=true fn=-1
2021-06-07 11:11:37.902 31680-31680/games.moisoni.evfp D/ScrollView: onsize change changed
2021-06-07 11:11:37.902 31680-31680/games.moisoni.evfp I/ViewRootImpl@d0eac04[SettingsActivity]: [DP] dp(1) 1 android.view.ViewRootImpl.reportNextDraw:10951 android.view.ViewRootImpl.performTraversals:3845 android.view.ViewRootImpl.doTraversal:2618
2021-06-07 11:11:37.902 31680-31680/games.moisoni.evfp I/ViewRootImpl@d0eac04[SettingsActivity]: [DP] pd() Asnyc report
2021-06-07 11:11:37.922 31680-31680/games.moisoni.evfp I/ViewRootImpl@d0eac04[SettingsActivity]: [DP] pdf(0) 1 android.view.ViewRootImpl.lambda$performDraw$ViewRootImpl:4668 android.view.-$$Lambda$ViewRootImpl$DJd0VUYJgsebcnSohO6h8zc_ONI.run:6 android.os.Handler.handleCallback:938
2021-06-07 11:11:37.922 31680-31680/games.moisoni.evfp I/ViewRootImpl@d0eac04[SettingsActivity]: [DP] rdf()
2021-06-07 11:11:37.927 31680-31680/games.moisoni.evfp I/ViewRootImpl@ef10be4[MainActivity]: MSG_WINDOW_FOCUS_CHANGED 0 1
2021-06-07 11:11:37.928 31680-32758/games.moisoni.evfp D/IapConnector: Billing service: connected
2021-06-07 11:11:37.933 31680-32423/games.moisoni.evfp D/FA: Connected to remote service
2021-06-07 11:11:37.935 31680-32423/games.moisoni.evfp V/FA: Processing queued up service tasks: 4
2021-06-07 11:11:37.938 31680-32759/games.moisoni.evfp D/IapConnector: Query SKU Details: data found
2021-06-07 11:11:37.938 31680-32758/games.moisoni.evfp D/IapConnector: Subscriptions support check: success
2021-06-07 11:11:37.939 31680-32759/games.moisoni.evfp D/IapConnector: SKU FOUND: test_purchase7
2021-06-07 11:11:37.940 31680-32759/games.moisoni.evfp D/CompatibilityChangeReporter: Compat change id reported: 147798919; UID 10745; state: ENABLED
2021-06-07 11:11:37.948 31680-31680/games.moisoni.evfp D/SurfaceControl: hide : mNativeObject = 531070505712 - sc.mNativeObject = 530532840928 - Surface(name=Surface(name=a8e3812 NavigationBar0)/@0x38b32cd - animation-leash)/@0x2a3e45d
2021-06-07 11:11:37.948 31680-31680/games.moisoni.evfp D/SurfaceControl: nativeSetFlags Done : Surface(name=Surface(name=a8e3812 NavigationBar0)/@0x38b32cd - animation-leash)/@0x2a3e45d
2021-06-07 11:11:37.949 31680-31680/games.moisoni.evfp D/SurfaceControl: hide : mNativeObject = 531070497088 - sc.mNativeObject = 530532838464 - Surface(name=Surface(name=e511e35 StatusBar)/@0xbf071b - animation-leash)/@0xc4ac1d2
2021-06-07 11:11:37.949 31680-31680/games.moisoni.evfp D/SurfaceControl: nativeSetFlags Done : Surface(name=Surface(name=e511e35 StatusBar)/@0xbf071b - animation-leash)/@0xc4ac1d2
2021-06-07 11:11:37.951 31680-31680/games.moisoni.evfp I/SurfaceControl: release : mNativeObject = 530532781344 - Surface(name=Surface(name=37630c6 InputMethod)/@0xd11e623 - animation-leash)/@0xe000b05 / android.view.-$$Lambda$Rl1VZmNJ0VZDLK0BAbaVGis0rrA.accept:2 android.view.InsetsSourceControl.release:170 android.view.InsetsSourceConsumer.setControl:202 android.view.ImeInsetsSourceConsumer.setControl:154
2021-06-07 11:11:37.951 31680-31680/games.moisoni.evfp I/SurfaceControl: nativeRelease nativeObject s[530532781344]
2021-06-07 11:11:37.951 31680-31680/games.moisoni.evfp I/SurfaceControl: nativeRelease nativeObject e[530532781344]
2021-06-07 11:11:37.951 31680-31680/games.moisoni.evfp I/SurfaceControl: release : mNativeObject = 530532786048 - Surface(name=Surface(name=a8e3812 NavigationBar0)/@0x38b32cd - animation-leash)/@0xd4eb2a3 / android.view.-$$Lambda$Rl1VZmNJ0VZDLK0BAbaVGis0rrA.accept:2 android.view.InsetsSourceControl.release:170 android.view.InsetsSourceConsumer.setControl:202 android.view.InsetsController.onControlsChanged:833
2021-06-07 11:11:37.951 31680-31680/games.moisoni.evfp I/SurfaceControl: nativeRelease nativeObject s[530532786048]
2021-06-07 11:11:37.951 31680-31680/games.moisoni.evfp I/SurfaceControl: nativeRelease nativeObject e[530532786048]
2021-06-07 11:11:37.951 31680-31680/games.moisoni.evfp I/SurfaceControl: release : mNativeObject = 530532732064 - Surface(name=Surface(name=e511e35 StatusBar)/@0xbf071b - animation-leash)/@0x201e9a0 / android.view.-$$Lambda$Rl1VZmNJ0VZDLK0BAbaVGis0rrA.accept:2 android.view.InsetsSourceControl.release:170 android.view.InsetsSourceConsumer.setControl:202 android.view.InsetsController.onControlsChanged:833
2021-06-07 11:11:37.951 31680-31680/games.moisoni.evfp I/SurfaceControl: nativeRelease nativeObject s[530532732064]
2021-06-07 11:11:37.951 31680-31680/games.moisoni.evfp I/SurfaceControl: nativeRelease nativeObject e[530532732064]
2021-06-07 11:11:37.955 31680-31680/games.moisoni.evfp I/ViewRootImpl@d0eac04[SettingsActivity]: MSG_WINDOW_FOCUS_CHANGED 1 1
2021-06-07 11:11:37.955 31680-31680/games.moisoni.evfp D/InputMethodManager: prepareNavigationBarInfo() DecorView@25a7196[SettingsActivity]
2021-06-07 11:11:37.955 31680-31680/games.moisoni.evfp D/InputMethodManager: getNavigationBarColor() -16711423
2021-06-07 11:11:37.957 31680-31680/games.moisoni.evfp D/InputMethodManager: prepareNavigationBarInfo() DecorView@25a7196[SettingsActivity]
2021-06-07 11:11:37.957 31680-31680/games.moisoni.evfp D/InputMethodManager: getNavigationBarColor() -16711423
2021-06-07 11:11:37.957 31680-31680/games.moisoni.evfp V/InputMethodManager: Starting input: tba=games.moisoni.evfp ic=null mNaviBarColor -16711423 mIsGetNaviBarColorSuccess true , NavVisible : false , NavTrans : false
2021-06-07 11:11:37.957 31680-31680/games.moisoni.evfp D/InputMethodManager: startInputInner - Id : 0
2021-06-07 11:11:37.957 31680-31680/games.moisoni.evfp I/InputMethodManager: startInputInner - mService.startInputOrWindowGainedFocus
2021-06-07 11:11:37.959 31680-31680/games.moisoni.evfp D/InputTransport: Input channel constructed: 'ClientS', fd=221
2021-06-07 11:11:37.959 31680-31680/games.moisoni.evfp D/InputTransport: Input channel destroyed: 'ClientS', fd=111
2021-06-07 11:11:37.998 31680-31680/games.moisoni.evfp I/SurfaceControl: release : mNativeObject = 530532838240 - Surface(name=Surface(name=37630c6 InputMethod)/@0xd11e623 - animation-leash)/@0x46f291e / android.view.-$$Lambda$Rl1VZmNJ0VZDLK0BAbaVGis0rrA.accept:2 android.view.InsetsSourceControl.release:170 android.view.InsetsSourceConsumer.setControl:202 android.view.ImeInsetsSourceConsumer.setControl:154
2021-06-07 11:11:37.998 31680-31680/games.moisoni.evfp I/SurfaceControl: nativeRelease nativeObject s[530532838240]
2021-06-07 11:11:37.998 31680-31680/games.moisoni.evfp I/SurfaceControl: nativeRelease nativeObject e[530532838240]
2021-06-07 11:11:37.999 31680-31680/games.moisoni.evfp D/SurfaceControl: hide : mNativeObject = 531069881968 - sc.mNativeObject = 530532839920 - Surface(name=Surface(name=37630c6 InputMethod)/@0xd11e623 - animation-leash)/@0xd0fd5ff
2021-06-07 11:11:37.999 31680-31680/games.moisoni.evfp D/SurfaceControl: nativeSetFlags Done : Surface(name=Surface(name=37630c6 InputMethod)/@0xd11e623 - animation-leash)/@0xd0fd5ff
2021-06-07 11:11:38.001 31680-31680/games.moisoni.evfp I/SurfaceControl: release : mNativeObject = 530532840928 - Surface(name=Surface(name=a8e3812 NavigationBar0)/@0x38b32cd - animation-leash)/@0x2a3e45d / android.view.-$$Lambda$Rl1VZmNJ0VZDLK0BAbaVGis0rrA.accept:2 android.view.InsetsSourceControl.release:170 android.view.InsetsSourceConsumer.setControl:202 android.view.InsetsController.onControlsChanged:833
2021-06-07 11:11:38.001 31680-31680/games.moisoni.evfp I/SurfaceControl: nativeRelease nativeObject s[530532840928]
2021-06-07 11:11:38.001 31680-31680/games.moisoni.evfp I/SurfaceControl: nativeRelease nativeObject e[530532840928]
2021-06-07 11:11:38.001 31680-31680/games.moisoni.evfp I/SurfaceControl: release : mNativeObject = 530532838464 - Surface(name=Surface(name=e511e35 StatusBar)/@0xbf071b - animation-leash)/@0xc4ac1d2 / android.view.-$$Lambda$Rl1VZmNJ0VZDLK0BAbaVGis0rrA.accept:2 android.view.InsetsSourceControl.release:170 android.view.InsetsSourceConsumer.setControl:202 android.view.InsetsController.onControlsChanged:833
2021-06-07 11:11:38.001 31680-31680/games.moisoni.evfp I/SurfaceControl: nativeRelease nativeObject s[530532838464]
2021-06-07 11:11:38.001 31680-31680/games.moisoni.evfp I/SurfaceControl: nativeRelease nativeObject e[530532838464]
2021-06-07 11:11:38.185 31680-31680/games.moisoni.evfp I/ViewRootImpl@ef10be4[MainActivity]: stopped(true) old=false
2021-06-07 11:11:38.187 31680-31680/games.moisoni.evfp I/SurfaceControl: release : mNativeObject = 530532758608 - Surface(name=games.moisoni.evfp/games.moisoni.evfp.MainActivity$_31680)/@0xf744215 / android.view.ViewRootImpl.destroySurface:2484 android.view.ViewRootImpl.setWindowStopped:2332 android.view.WindowManagerGlobal.setStoppedState:741 android.app.Activity.performStop:8423
2021-06-07 11:11:38.187 31680-31680/games.moisoni.evfp I/SurfaceControl: nativeRelease nativeObject s[530532758608]
2021-06-07 11:11:38.187 31680-31680/games.moisoni.evfp I/SurfaceControl: nativeRelease nativeObject e[530532758608]
2021-06-07 11:11:38.202 31680-32437/games.moisoni.evfp W/libEGL: EGLNativeWindowType 0x7ca63be2a0 disconnect failed
2021-06-07 11:11:38.206 31680-31680/games.moisoni.evfp I/SurfaceControl: assignNativeObject: nativeObject = 0 Surface(name=null)/@0xf744215 / android.view.SurfaceControl.readFromParcel:1115 android.view.IWindowSession$Stub$Proxy.relayout:1810 android.view.ViewRootImpl.relayoutWindow:9005 android.view.ViewRootImpl.performTraversals:3360 android.view.ViewRootImpl.doTraversal:2618 android.view.ViewRootImpl$TraversalRunnable.run:9965 android.view.Choreographer$CallbackRecord.run:1010 android.view.Choreographer.doCallbacks:809 android.view.Choreographer.doFrame:744 android.view.Choreographer$FrameDisplayEventReceiver.run:995
2021-06-07 11:11:38.206 31680-31680/games.moisoni.evfp I/SurfaceControl: assignNativeObject: nativeObject = 0 Surface(name=null)/@0xda8bd8d / android.view.SurfaceControl.readFromParcel:1115 android.view.IWindowSession$Stub$Proxy.relayout:1820 android.view.ViewRootImpl.relayoutWindow:9005 android.view.ViewRootImpl.performTraversals:3360 android.view.ViewRootImpl.doTraversal:2618 android.view.ViewRootImpl$TraversalRunnable.run:9965 android.view.Choreographer$CallbackRecord.run:1010 android.view.Choreographer.doCallbacks:809 android.view.Choreographer.doFrame:744 android.view.Choreographer$FrameDisplayEventReceiver.run:995
2021-06-07 11:11:38.207 31680-31680/games.moisoni.evfp I/ViewRootImpl@ef10be4[MainActivity]: Relayout returned: old=(0,144,1440,3040) new=(0,144,1440,3040) req=(1440,2896)8 dur=4 res=0x5 s={false 0} ch=false fn=-1
在 V4.0 中,BillingClient 方法回调在后台线程中调用,而不是在主线程中调用。
解决方法是在 BillingClient 回调中将线程更改为 Main。
在 UI 线程中用 Toast 调用方法 processPurchases()。
@researcher answer 是一个完全有效的选项,但对我来说,解决这个问题的最佳方法是触发 main UI thread
上的 listeners
,而不是更改所有 [=] 的线程14=] 回调。
他的回答将继续被接受,因为他发现了问题。
为了在 UI 线程上调用 listeners
我创建了一个方法:
private Handler findUiHandler() {
return new Handler(Looper.getMainLooper());
}
然后像这样调用监听器:
findUiHandler().post(() -> billingEventListener.onProductsFetched(fetchedSkuInfo));