Xamarin.InAppBilling - "Error available inventory: System.NullReferenceException"

Xamarin.InAppBilling - "Error available inventory: System.NullReferenceException"

注意:我主要在这里发布这个问题,以便它被 google 编入索引并免除一些可怜的灵魂深入挖掘这个混乱的麻烦只是为了发现这不是他们自己的错。这个问题的一个可接受的答案是我可以使用的解决方法,或者 Xamarin/SimpleJson 的某人确认这是一个错误,它将被修复。

我已经成功使用 Xamarin.InAppBilling 一年多了(在真正的 Google Play 商店购买时效果很好)。我们最近开始重新测试此功能(由于重构)并在查询我们的产品库存时遇到此异常(即使在使用静态响应测试产品 Skus 时):

Error Available Inventory: System.NullReferenceException: Object reference not set to an instance of an object
  at Xamarin.InAppBilling.PocoJsonSerializerStrategy.DeserializeObject (System.Object value, System.Type type) [0x0047a] in <filename unknown>:0 
  at Xamarin.InAppBilling.SimpleJson.DeserializeObject (System.String json, System.Type type, IJsonSerializerStrategy jsonSerializerStrategy) [0x0003d] in <filename unknown>:0 
  at Xamarin.InAppBilling.SimpleJson.DeserializeObject[T] (System.String json) [0x00000] in <filename unknown>:0 
  at System.Linq.Enumerable+WhereSelectEnumerableIterator`2[TSource,TResult].MoveNext () [0x00064] in /Users/builder/data/lanes/2098/3efa14c4/source/mono/external/referencesource/System.Core/System/Linq/Enumerable.cs:285 
  at System.Collections.Generic.List`1[T]..ctor (IEnumerable`1 collection) [0x0008b] in /Users/builder/data/lanes/2098/3efa14c4/source/mono/external/referencesource/mscorlib/system/collections/generic/list.cs:105 
  at System.Linq.Enumerable.ToList[TSource] (IEnumerable`1 source) [0x00011] in /Users/builder/data/lanes/2098/3efa14c4/source/mono/external/referencesource/System.Core/System/Linq/Enumerable.cs:835 
  at Xamarin.InAppBilling.InAppBillingHandler+<QueryInventoryAsync>c__AnonStorey0.<>m__0 () [0x00092] in <filename unknown>:0 

我已经推翻了 Xamarin 的应用内结算代码,尤其是 Xamarin.InAppBilling.InAppBillingHandler.QueryInventoryAsync(IList<string> skuList, string itemType) 方法。

它所做的是查询 Google Play Billing API 以获得 sku 详细信息,然后反序列化返回的 DETAILS_LIST json 对象。这是代码抛出 NullReferenceException 的地方。所有这些都超出了我的控制范围,因此我使用了此代码的修改副本来获取原始 json 并且果然它对我来说看起来相当不错。代码:

var service = _serviceConnection.Service;
Task.Factory.StartNew<IList<Product>>( () => {
    IList<Product> result;
    try
    {
        Bundle bundle = new Bundle();
        bundle.PutStringArrayList( "ITEM_ID_LIST", skus );
        Bundle skuDetails = service.GetSkuDetails( 3, this.Activity.PackageName, ItemType.Product, bundle );
        int responseCode = skuDetails.GetInt( "RESPONSE_CODE" );
        if (responseCode != 0)
        {
            ViewModel.ShowTransactionError( "Error querying inventory: " + TranslateResponseCode( responseCode ) );
            result = null;
        }
        else
        {
            IList<string> stringArrayList = skuDetails.GetStringArrayList( "DETAILS_LIST" );
            result = null;
            if (stringArrayList != null)
            {
                Console.WriteLine("DETAILS_LIST: " + string.Join("\n - ", stringArrayList));
                result = stringArrayList.Select(x => JsonConvert.DeserializeObject<Product>(x)).ToList();
            }                        
        }
    } catch (Exception ex)
    {
        ViewModel.ShowTransactionError( "Error querying inventory: " + ex.ToString() );
        result = null;
    }
    return result;
} )

Json:

DETAILS_LIST: {"title":"Sample Title","price":"0,93 €","type":"inapp","description":"Sample description for product: android.test.canceled.","price_amount_micros":933038,"price_currency_code":"EUR","productId":"android.test.canceled"}
 - {"title":"Sample Title","price":"0,93 €","type":"inapp","description":"Sample description for product: android.test.item_unavailable.","price_amount_micros":933038,"price_currency_code":"EUR","productId":"android.test.item_unavailable"}
 - {"title":"Sample Title","price":"0,93 €","type":"inapp","description":"Sample description for product: android.test.purchased.","price_amount_micros":933038,"price_currency_code":"EUR","productId":"android.test.purchased"}
 - {"title":"Sample Title","price":"0,93 €","type":"inapp","description":"Sample description for product: android.test.refunded.","price_amount_micros":933038,"price_currency_code":"EUR","productId":"android.test.refunded"}
 - {"title":"Boat Upgrade (Rowing in Motion - Solo)","price":"69,90 €","type":"inapp","description":"Analyze and Record an unlimited number of strokes per rowing session.","price_amount_micros":69900000,"price_currency_code":"EUR","productId":"com.rowinginmotion.mobile.boatapp.droid.solo.boat"}
 - {"title":"Coach Upgrade (Rowing in Motion - Solo)","price":"98,77 €","type":"inapp","description":"Receive unlimited live data from a boat using a mobile WiFi.","price_amount_micros":98770000,"price_currency_code":"EUR","productId":"com.rowinginmotion.mobile.boatapp.droid.solo.coach"}

所以我想 SimpleJson/PocoJson 里面的任何序列化程序都有问题。对我来说,这看起来像是库中的错误,而不是我这边的错误(通过他们的清单和这里看起来很不错的所有内容)。

我会使用 JSON.NET 问题是他们的 Product class 显然没有默认构造函数(什么?!,我已经使用 IL 多年但从未见过喜欢这个),但有一个 CompilerGeneratedAttribute 卡在上面。我的产品需要是那个 class 的实例,这样我就可以将它们传回给他们的 API 但是...

linker hits again. The PocoJsonSerializerStrategy uses plenty of reflection so this was my second guess, after working through the long list of things you need to get right for IAPs to work

将此添加到您的 .csproj,您就可以开始了。

<AndroidLinkSkip>Xamarin.InAppBilling</AndroidLinkSkip>

Xamarin 没有制作自己的库链接器证明,这真是令人失望...

这个问题很容易让我花费一两天的时间,特别是因为有太多 subtle ways 应用内结算可能会中断,您应该首先检查(特别是,我们移动了很多我们的 IAP 逻辑从 Activity 到片段,因此也必须对其进行测试)。

旁注:我徒劳地等待了 3x2 小时 Google Play Alpha 通道更新我的测试设备上可用的检测构建。对于 me,我可以使用从 Xamarin Studio 部署的构建(甚至调试构建)成功测试和 运行 IAP。并不意味着这也适用于您。