首页 > 解决方案 > 如何在 Xamarin.Android 中异步实现 Android In App BillingClient

问题描述

我正在尝试在 c# 中参考 Android文档实现下面的 java 代码

List<String> skuList = new ArrayList<> ();
skuList.add("premium_upgrade");
skuList.add("gas");
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(skuList).setType(SkuType.INAPP);
billingClient.querySkuDetailsAsync(params.build(),
    new SkuDetailsResponseListener() {
        @Override
        public void onSkuDetailsResponse(BillingResult billingResult,
                List<SkuDetails> skuDetailsList) {
            // Process the result.
        }
    });

我这里有 2 个问题。我认为我会在一个单独的线程上运行此代码,而不是像下面这样的 UI 线程,以在网络连接完成时保持我的 ui 响应。这是正确的方法吗?QuerySkuDetailsAsync 被称为异步,但不作为异步实现。这应该如何工作以及如何在 c# 中处理,因为它会触发并忘记,但侦听器会处理响应。

public async Task<List<InAppBillingProduct>> GetProductsAsync(List<string> ProductIds)
        {
 var getSkuDetailsTask = Task.Factory.StartNew(() =>
            {

                var prms = SkuDetailsParams.NewBuilder();
                var type =   BillingClient.SkuType.Inapp;
                prms.SetSkusList(ProductIds).SetType(type);

                BillingClient.QuerySkuDetailsAsync(prms.Build(), new SkuDetailsResponseListener());

                return InAppBillingProducts;
            });
     return await getSkuDetailsTask;
        }

关于如何处理听众的第二个问题如下。如何从侦听器返回值。我需要 InAppBillingProduct 对象的返回列表。

 public class SkuDetailsResponseListener : Java.Lang.Object, ISkuDetailsResponseListener
    {
        public void OnSkuDetailsResponse(BillingResult billingResult, IList<SkuDetails> skus)
        {
             if (billingResult.ResponseCode == BillingResponseCode.Ok)
            {
                   // get list of Products here and return
            }
        }
    }

标签: xamarinxamarin.androidin-app-billing

解决方案


FYI. This is how I did it. This is not a complete code but this will give you and idea.

Listener - PCL
============
private async Task EventClicked()
{
  var skuList = new List<string>();
  skuList.Add("[nameofsubscriptionfoundinyourgoogleplay]");

  if (await _billingClientLifecycle.Initialize(skuList, DisconnectedConnection))
        {
            var firstProduct = _billingClientLifecycle?.ProductsInStore?.FirstOrDefault();

            if (firstProduct != null)
            {
                //purchase here
            }
        }
}

private void DisconnectedConnection()
    { 
        //Todo.alfon. handle disconnection here...
    }


Interface - PCL
===========
public interface IInAppBillingMigratedNew
{
    List<InAppBillingPurchase> PurchasedProducts { get; set; }
    List<InAppBillingProduct> ProductsInStore { get; set; }
    Task<bool> Initialize(List<String> skuList, Action onDisconnected = null);
}


Dependency - Platform Droid
===============
[assembly: XF.Dependency(typeof(InAppBillingMigratedNew))]
public class InAppBillingMigratedNew : Java.Lang.Object, IBillingClientStateListener
    , ISkuDetailsResponseListener, IInAppBillingMigratedNew
{
    private Activity Context => CrossCurrentActivity.Current.Activity 
        ?? throw new NullReferenceException("Current Context/Activity is null");
    private BillingClient _billingClient;
    private List<string> _skuList = new List<string>();
    private TaskCompletionSource<bool> _tcsInitialized;
    private Action _disconnectedAction;
    private Dictionary<string, SkuDetails> _skusWithSkuDetails = new Dictionary<string, SkuDetails>();

    public List<InAppBillingPurchase> PurchasedProducts { get; set; }
    public List<InAppBillingProduct> ProductsInStore { get; set; }
    public IntPtr Handle => throw new NotImplementedException();

    public Task<bool> Initialize(List<string> skuList, Action disconnectedAction = null)
    {
        _disconnectedAction = disconnectedAction;
        _tcsInitialized = new TaskCompletionSource<bool>();
        var taskInit = _tcsInitialized.Task;
        _skuList = skuList;
        _billingClient = BillingClient.NewBuilder(Context)
           .SetListener(this)
           .EnablePendingPurchases()
           .Build();

        if (!_billingClient.IsReady)
        {
            _billingClient.StartConnection(this);
        }

        return taskInit;
    }

    #region IBillingClientStateListener

    public void OnBillingServiceDisconnected()
    {
        Console.WriteLine($"Connection disconnected.");
        _tcsInitialized?.TrySetResult(false);
        _disconnectedAction?.Invoke();
    }

    public void OnBillingSetupFinished(BillingResult billingResult)
    {  
        var responseCode = billingResult.ResponseCode;
        var debugMessage = billingResult.DebugMessage;

        if (responseCode == BillingResponseCode.Ok)
        {
            QuerySkuDetails();
            QueryPurchases();
            _tcsInitialized?.TrySetResult(true);
        }
        else
        {
            Console.WriteLine($"Failed connection {debugMessage}");
            _tcsInitialized?.TrySetResult(false);
        }
    }

    #endregion

    #region ISkuDetailsResponseListener

    public void OnSkuDetailsResponse(BillingResult billingResult, IList<SkuDetails> skuDetailsList)
    {
        if (billingResult == null)
        {
            Console.WriteLine("onSkuDetailsResponse: null BillingResult");
            return;
        }

        var responseCode = billingResult.ResponseCode;
        var debugMessage = billingResult.DebugMessage;

        switch (responseCode)
        {
            case BillingResponseCode.Ok:
                if (skuDetailsList == null)
                {
                    _skusWithSkuDetails.Clear();
                }
                else
                {
                    if (skuDetailsList.Count > 0)
                    {
                        ProductsInStore = new List<InAppBillingProduct>();
                    }

                    foreach (var skuDetails in skuDetailsList)
                    {
                        _skusWithSkuDetails.Add(skuDetails.Sku, skuDetails);

                        //ToDo.alfon. make use mapper here
                        ProductsInStore.Add(new InAppBillingProduct
                        {
                            Name = skuDetails.Title,
                            Description = skuDetails.Description,
                            ProductId = skuDetails.Sku,
                            CurrencyCode = skuDetails.PriceCurrencyCode,
                            LocalizedIntroductoryPrice = skuDetails.IntroductoryPrice,
                            LocalizedPrice = skuDetails.Price,
                            MicrosIntroductoryPrice = skuDetails.IntroductoryPriceAmountMicros,
                            MicrosPrice = skuDetails.PriceAmountMicros                                
                        });
                    }
                }
                break;
            case BillingResponseCode.ServiceDisconnected:
            case BillingResponseCode.ServiceUnavailable:
            case BillingResponseCode.BillingUnavailable:
            case BillingResponseCode.ItemUnavailable:
            case BillingResponseCode.DeveloperError:
            case BillingResponseCode.Error:
                Console.WriteLine("onSkuDetailsResponse: " + responseCode + " " + debugMessage);
                break;
            case BillingResponseCode.UserCancelled:
                Console.WriteLine("onSkuDetailsResponse: " + responseCode + " " + debugMessage);
                break;
            // These response codes are not expected.
            case BillingResponseCode.FeatureNotSupported:
            case BillingResponseCode.ItemAlreadyOwned:
            case BillingResponseCode.ItemNotOwned:
            default:
                Console.WriteLine("onSkuDetailsResponse: " + responseCode + " " + debugMessage);
                break;
        }
    }

    #endregion

    #region Helper Methods Private

    private void ProcessPurchases(List<Purchase> purchasesList)
    {
        if (purchasesList == null)
        {
            Console.WriteLine("No purchases done.");
            return;
        }

        if (IsUnchangedPurchaseList(purchasesList))
        {
            Console.WriteLine("Purchases has not changed.");
            return;
        }

        _purchases.AddRange(purchasesList);
        PurchasedProducts = _purchases.Select(sku => new InAppBillingPurchase
        {
            PurchaseToken = sku.PurchaseToken
        })?.ToList();

        if (purchasesList != null)
        {
            LogAcknowledgementStatus(purchasesList);
        }
    }

    private bool IsUnchangedPurchaseList(List<Purchase> purchasesList)
    {
        // TODO: Optimize to avoid updates with identical data.
        return false;
    }

    private void LogAcknowledgementStatus(List<Purchase> purchasesList)
    {
        int ack_yes = 0;
        int ack_no = 0;

        foreach (var purchase in purchasesList)
        {
            if (purchase.IsAcknowledged)
            {
                ack_yes++;
            }
            else
            {
                ack_no++;
            }
        }
        //Log.d(TAG, "logAcknowledgementStatus: acknowledged=" + ack_yes +
        //        " unacknowledged=" + ack_no);
    }

    private void QuerySkuDetails()
    {
        var parameters = SkuDetailsParams
            .NewBuilder()
            .SetType(BillingClient.SkuType.Subs)
            .SetSkusList(_skuList)
            .Build();
        _billingClient.QuerySkuDetailsAsync(parameters, this);
    }

    private void QueryPurchases()
    {
        if (!_billingClient.IsReady)
        {
            Console.WriteLine("queryPurchases: BillingClient is not ready");
        }

        var result = _billingClient.QueryPurchases(BillingClient.SkuType.Subs);
        ProcessPurchases(result?.PurchasesList?.ToList());
    }

    #endregion
}

推荐阅读