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() 的变化。

到目前为止一切顺利。继续解决问题...

在我的图书馆中,我有一个名为 onProductsFetchedlistener,它将提供一个 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));