2

通过应用内购买购买商品时,我的应用有时会崩溃。

大多数情况下它工作正常,但有时应用程序崩溃而没有任何错误(我正在调试模式下测试)。

为了设置应用内购买(非消耗品),我使用了以下示例: https ://github.com/conceptdev/xamarin-samples/tree/master/InAppPurchase/NonConsumables

处理应用内购买的类如下所示:

public class InAppPurchaseManager : SKProductsRequestDelegate {
    public static NSString InAppPurchaseManagerProductsFetchedNotification = new NSString("InAppPurchaseManagerProductsFetchedNotification");
    public static NSString InAppPurchaseManagerTransactionFailedNotification = new NSString("InAppPurchaseManagerTransactionFailedNotification");
    public static NSString InAppPurchaseManagerTransactionSucceededNotification = new NSString("InAppPurchaseManagerTransactionSucceededNotification");
    public static NSString InAppPurchaseManagerRequestFailedNotification = new NSString("InAppPurchaseManagerRequestFailedNotification");

    SKProductsRequest productsRequest;
    CustomPaymentObserver theObserver;
    SKProduct[] products;

    public static NSAction Done {get;set;}

    public InAppPurchaseManager ()
    {
        theObserver = new CustomPaymentObserver(this);
        SKPaymentQueue.DefaultQueue.AddTransactionObserver(theObserver);
    }

    // received response to RequestProductData - with price,title,description info
    public override void ReceivedResponse (SKProductsRequest request, SKProductsResponse response)
    {
        products = response.Products;

        NSDictionary userInfo = null;
        if (products.Length > 0) {
            NSObject[] productIdsArray = new NSObject[response.Products.Length];
            NSObject[] productsArray = new NSObject[response.Products.Length];
            for (int i = 0; i < response.Products.Length; i++) {
                productIdsArray[i] = new NSString(response.Products[i].ProductIdentifier);
                productsArray[i] = response.Products[i];
            }
            userInfo = NSDictionary.FromObjectsAndKeys (productsArray, productIdsArray);
        }
        NSNotificationCenter.DefaultCenter.PostNotificationName(InAppPurchaseManagerProductsFetchedNotification,this,userInfo);

        foreach (string invalidProductId in response.InvalidProducts) {
            Console.WriteLine("Invalid product id: " + invalidProductId );
        }
    }

    // request multiple products at once
    public void RequestProductData (List<string> productIds)
    {
        var array = new NSString[productIds.Count];
        for (var i = 0; i < productIds.Count; i++) {
            array[i] = new NSString(productIds[i]);
        }
        NSSet productIdentifiers = NSSet.MakeNSObjectSet<NSString>(array);          

        //set up product request for in-app purchase
        productsRequest  = new SKProductsRequest(productIdentifiers);
        productsRequest.Delegate = this; // SKProductsRequestDelegate.ReceivedResponse
        productsRequest.Start();
        Console.WriteLine ("BEREIKT");
    }



    // Verify that the iTunes account can make this purchase for this application
    public bool CanMakePayments()
    {
        return SKPaymentQueue.CanMakePayments;  
    }

    public void PurchaseProduct(string appStoreProductId)
    {
        Console.WriteLine("PurchaseProduct " + appStoreProductId);

        SKPayment payment = SKPayment.PaymentWithProduct (appStoreProductId);   

        SKPaymentQueue.DefaultQueue.AddPayment (payment);
    }


    public void CompleteTransaction (SKPaymentTransaction transaction)
    {
        Console.WriteLine ("CompleteTransaction " + transaction.TransactionIdentifier);
        var productId = transaction.Payment.ProductIdentifier;
        // Register the purchase, so it is remembered for next time
        //PhotoFilterManager.Purchase(productId);
        UserDefaults.Purchase(productId);

        FinishTransaction (transaction, true);

        //Show Dialog
        new UIAlertView("Succes", "De aankoop is gelukt." +
                        "\n Je kunt de gekozen categorieën nu spelen.", null, "OK", null).Show();
    /*
        if (ReceiptValidation.VerificationController.SharedInstance.VerifyPurchase (transaction)) {
            Console.WriteLine ("Verified!");
            // Register the purchase, so it is remembered for next time
            PhotoFilterManager.Purchase(productId);
            FinishTransaction (transaction, true);
        } else {
            Console.WriteLine ("NOT Verified :(");
            FinishTransaction (transaction, false);
        }
            */
    }
    public void RestoreTransaction (SKPaymentTransaction transaction)
    {
        // Restored Transactions always have an 'original transaction' attached
        Console.WriteLine("RestoreTransaction " + transaction.TransactionIdentifier + "; OriginalTransaction " + transaction.OriginalTransaction.TransactionIdentifier);
        var productId = transaction.OriginalTransaction.Payment.ProductIdentifier;
        // Register the purchase, so it is remembered for next time
        //PhotoFilterManager.Purchase(productId); // it's as though it was purchased again
        UserDefaults.Purchase(productId);
        FinishTransaction(transaction, true);
    }

    public void FailedTransaction (SKPaymentTransaction transaction)
    {
        //SKErrorPaymentCancelled == 2
        if (transaction.Error.Code == 2) // user cancelled
            Console.WriteLine("User CANCELLED FailedTransaction Code=" + transaction.Error.Code + " " + transaction.Error.LocalizedDescription);
        else // error!
            Console.WriteLine("FailedTransaction Code=" + transaction.Error.Code + " " + transaction.Error.LocalizedDescription);

        FinishTransaction(transaction,false);

        //Show Dialog
        new UIAlertView("Helaas", "De aankoop is mislukt." +
                        "\n Probeer het op een later tijdstip nogmaals a.u.b.", null, "OK", null).Show();
    }

    public void FinishTransaction(SKPaymentTransaction transaction, bool wasSuccessful)
    {
        Console.WriteLine("FinishTransaction " + wasSuccessful);
        // remove the transaction from the payment queue.
        SKPaymentQueue.DefaultQueue.FinishTransaction(transaction);     // THIS IS IMPORTANT - LET'S APPLE KNOW WE'RE DONE !!!!

        using (var pool = new NSAutoreleasePool()) {
            NSDictionary userInfo = NSDictionary.FromObjectsAndKeys(new NSObject[] {transaction},new NSObject[] {new NSString("transaction")});
            if (wasSuccessful) {
                // send out a notification that we’ve finished the transaction
                NSNotificationCenter.DefaultCenter.PostNotificationName(InAppPurchaseManagerTransactionSucceededNotification,this,userInfo);
            } else {
                // send out a notification for the failed transaction
                NSNotificationCenter.DefaultCenter.PostNotificationName(InAppPurchaseManagerTransactionFailedNotification,this,userInfo);
            }
        }
    }

    /// <summary>
    /// Probably could not connect to the App Store (network unavailable?)
    /// </summary>
    public override void RequestFailed (SKRequest request, NSError error)
    {
        Console.WriteLine (" ** InAppPurchaseManager RequestFailed() " + error.LocalizedDescription);
        using (var pool = new NSAutoreleasePool()) {
            NSDictionary userInfo = NSDictionary.FromObjectsAndKeys(new NSObject[] {error},new NSObject[] {new NSString("error")});
            // send out a notification for the failed transaction
            NSNotificationCenter.DefaultCenter.PostNotificationName(InAppPurchaseManagerRequestFailedNotification,this,userInfo);
        }
    }

    /// <summary>
    /// Restore any transactions that occurred for this Apple ID, either on 
    /// this device or any other logged in with that account.
    /// </summary>
    public void Restore()
    {
        Console.WriteLine (" ** InAppPurchaseManager Restore()");
        // theObserver will be notified of when the restored transactions start arriving <- AppStore
        SKPaymentQueue.DefaultQueue.RestoreCompletedTransactions();         
    }
}

什么可能导致崩溃?

仅供参考:我正在使用 Xamarin iOS 版本 6.3.4.36(测试版)。现在我使用这个 beta 版本,因为它解决了我在 Game Center 上遇到的问题。Xamarin 的稳定版本还没有解决这个问题。

PS。我读到我使用的示例没有实现 RECEIPT VERIFICATION。这是什么意思,是否有必要实施?

第一次更新:

有时我会收到此错误。

    mono-rt: Stacktrace:


    mono-rt:   at <unknown> <0xffffffff>

    mono-rt:   at (wrapper managed-to-native) MonoTouch.UIKit.UIApplication.UIApplicationMain (int,string[],intptr,intptr) <IL 0x0009f, 0xffffffff>

    mono-rt:   at MonoTouch.UIKit.UIApplication.Main (string[],string,string) [0x0004c] in /Developer/MonoTouch/Source/monotouch/src/UIKit/UIApplication.cs:38

    mono-rt:   at PP_IOS.Application.Main (string[]) [0x00001] in /Users/Mac01/Projects/PP/PP_IOS/Main.cs:19

    mono-rt:   at (wrapper runtime-invoke) <Module>.runtime_invoke_void_object (object,intptr,intptr,intptr) <IL 0x00050, 0xffffffff>

    mono-rt: 


    Native stacktrace:
    mono-rt: 
    =================================================================
    Got a SIGSEGV while executing native code. This usually indicates
    a fatal error in the mono runtime or one of the native libraries 
    used by your application.
    =================================================================

还有一次我得到这个错误:

    mono-rt: Stacktrace:


    mono-rt:   at <unknown> <0xffffffff>

    mono-rt:   at (wrapper managed-to-native) MonoTouch.ObjCRuntime.Messaging.void_objc_msgSend_IntPtr (intptr,intptr,intptr) <IL 0x00025, 0xffffffff>

    mono-rt:   at MonoTouch.StoreKit.SKPaymentQueue.AddPayment (MonoTouch.StoreKit.SKPayment) [0x0001c] in /Developer/MonoTouch/Source/monotouch/src/StoreKit/SKPaymentQueue.g.cs:107

    mono-rt:   at PP_IOS.InAppPurchaseManager.PurchaseProduct (string) [0x0001f] in /Users/Mac01/Projects/PP/PP_IOS/Utils/InAppPurchase/InAppPurchaseManager.cs:109

    mono-rt:   at PP_IOS.UpgradeScreen.<BuyCategoryArtistsAndSports>m__21 () [0x0003d] in /Users/Mac01/Projects/PP/PP_IOS/ControllersUniversal/UpgradeScreen.cs:171

    mono-rt:   at MonoTouch.Foundation.NSAsyncActionDispatcher.Apply () [0x00000] in /Developer/MonoTouch/Source/monotouch/src/shared/Foundation/NSAction.cs:87

    mono-rt:   at (wrapper runtime-invoke) object.runtime_invoke_void__this__ (object,intptr,intptr,intptr) <IL 0x0004e, 0xffffffff>

    mono-rt:   at <unknown> <0xffffffff>

    mono-rt:   at (wrapper managed-to-native) MonoTouch.UIKit.UIApplication.UIApplicationMain (int,string[],intptr,intptr) <IL 0x0009f, 0xffffffff>

    mono-rt:   at MonoTouch.UIKit.UIApplication.Main (string[],string,string) [0x0004c] in /Developer/MonoTouch/Source/monotouch/src/UIKit/UIApplication.cs:38

    mono-rt:   at PP_IOS.Application.Main (string[]) [0x00001] in /Users/Mac01/Projects/PP/PP_IOS/Main.cs:19

    mono-rt:   at (wrapper runtime-invoke) <Module>.runtime_invoke_void_object (object,intptr,intptr,intptr) <IL 0x00050, 0xffffffff>

    mono-rt: 
    Native stacktrace:


    mono-rt: 
    =================================================================
    Got a SIGSEGV while executing native code. This usually indicates
    a fatal error in the mono runtime or one of the native libraries 
    used by your application.
    =================================================================

第二次更新

我刚刚找到了问题发生的方式。购买和恢复应用内购买的按钮显示在模式视图中。当我重新打开模式视图并点击购买或恢复按钮时,应用程序似乎崩溃了。因此,当我第一次打开模态视图并点击购买和恢复按钮时,它(大部分时间)工作正常。但是当我重新打开模态视图并点击购买或恢复按钮时,应用程序崩溃并出现上面显示的错误。

有人熟悉这个吗?

4

3 回答 3

3

问题解决了!

关闭视图时,我必须删除 TransactionObserver。

    public override void ViewWillDisappear (bool animated)
    {
        base.ViewWillDisappear (animated);

        //Prevents crash when re-opening view
        SKPaymentQueue.DefaultQueue.RemoveTransactionObserver (theObserver);
    }
于 2013-05-01T14:30:23.900 回答
2

好吧,这真的很愚蠢。我会继续拿出邮票和钢笔来开始写给蒂姆库克的措辞强硬的信。

我从经验中发现SKProductsRequestDelegate.RequestFailed偶尔会返回 null NSError。这将导致方法的第一行出现空引用异常。这太可怕了,我不知道为什么会这样。

您可以将代码更改为:

public override void RequestFailed (SKRequest request, NSError error)
{
    if (error == null)
        Console.WriteLine("NSError is null!");
    else 
        Console.WriteLine (" ** InAppPurchaseManager RequestFailed() " + error.LocalizedDescription);

    using (var pool = new NSAutoreleasePool()) {
        NSDictionary userInfo = NSDictionary.FromObjectsAndKeys(new NSObject[] {error},new NSObject[] {new NSString("error")});
        // send out a notification for the failed transaction
        NSNotificationCenter.DefaultCenter.PostNotificationName(InAppPurchaseManagerRequestFailedNotification,this,userInfo);
    }
}

确保你也考虑到它的另一端NSNotificationCenter

顺便说一句,我不知道NSAutoreleasePool爵士乐是为了什么——你应该把它删掉。您是从一个非常古老的 MonoTouch 示例中得到的吗?

于 2013-05-01T01:09:22.110 回答
1

我也有同样的问题。我发现有

[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];

在 viewDidUnload 中不起作用。它需要在 viewWillDisappear 中。这似乎已经为我解决了。

于 2014-03-16T19:01:21.623 回答