When fetching GooglePlay products with FetchAdditionalProducts() in Unity IAP 4.4.1, FetchAdditionalProducts() is slow for some Google accounts.
From LogCat, it seems that where FetchAdditionalProducts() is being called, it intermittently outputs this log and the request is being retried.
09-28 14:57:07.304: I/Finsky(18962): [7570] guz.a(62): Billing preferred account via installer for app name: [account ID]
09-28 14:57:07.317: D/PaySecureElementClient(18962): Felica & Sidecar installed; migration flag enabled: returning isSecureElementAvailable = true!
09-28 14:57:07.320: D/BiometricService(1578): canAuthenticate: User=0, Caller=0, Authenticators=15
09-28 14:57:07.321: D/BiometricService(1578): Package: com.android.vending Authenticator ID: 0 Modality: 8 Reported Modality: 8 Status: 6
09-28 14:57:07.321: D/AuthService(1578): canAuthenticate, userId: 0, callingUserId: 0, authenticators: 15, result: 11```
Does anyone know what causes this and how to fix it?
Any help would be greatly appreciated. Thank you.
Hi Jeff,
Thanks for caring.
Here is a simplified version of our implemented code.
public class IAPSystem : MonoBehaviour, IStoreListener
{
bool fetching = false;
private IStoreController controller;
private void Awake()
{
Initialize();
}
public void Initialize()
{
var module = StandardPurchasingModule.Instance();
module.useFakeStoreUIMode = FakeStoreUIMode.StandardUser;
var builder = ConfigurationBuilder.Instance(module);
var initStoreId = "any StoreID for initialize";
builder.AddProduct(initStoreId, ProductType.NonConsumable);
UnityPurchasing.Initialize(this, builder);
}
public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
{
// OnInitialized is called on invoking FetchAdditionalProducts().
if (fetching) return;
this.controller = controller;
StartCoroutine(FetchProducts());
}
// This process is slow in some accounts.
// Usually takes less than a second to complete, but for some Google accounts it takes more than 20 seconds.
private IEnumerator FetchProducts()
{
// Get ProductDefinitions each 64 from the asset(There are more than 100 Non-Consumable products in all).
List<HashSet<ProductDefinition>> productsChunk = GetProductsChunk();
fetching = true;
foreach (var chunk in productsChunk) {
controller.FetchAdditionalProducts(chunk, () => { /* OnSuccess*/ }, (InitializationFailureReason r) => { /* OnFailed*/ });
yield return null;
}
}
}
I’m assuming that there is something wrong with some Google accounts. But I could not figure out the conditions under which they occurs.
So I’m going to make an inquiry to Google. Therefore, I need some information about implementation of FetchAdditionalProducts().
@Lilac-Envoy We currently use querySkuDetailsAsync which is marked as deprecated The new call that will eventually replace it is queryProductDetailsAsync. No ETA yet but hopefully later this year. More info about the change on Google’s side here.
Thanks a lot! It’s very helpful information!
I will make an inquiry to Google with the information you gave.
If I get any solution, I will put feedback here.
I have made an inquiry to Google but it was to no avail.
However, I got a solution and another problem.
Solution
The reason is that FetchAdditionalProducts() was being called separately for all products.
I newly found this log. Unity IAP: Unable to process purchase with transaction id: TransactionID because the product details associated with the purchased products were not found.
From looking at the Library/PackageCache code, I guessed that if a product is retrieved with split products from all, if there are no products purchased by the user there yet, it will be slower due to retries.
So I got products in bulk and it solved the slow down problem.
Another Problem
But I faced another problem.
I found that an account that purchased very large quantities of Non-Consumable merchandise ran out of memory.
I/adrive.appname(17087): WaitForGcToComplete blocked Alloc on HeapTrim for 94.002ms
I/adrive.appname(17087): Starting a blocking GC Alloc
I/adrive.appname(17087): WaitForGcToComplete blocked Alloc on HeapTrim for 59.212ms
I/adrive.appname(17087): Starting a blocking GC Alloc
I/adrive.appname(17087): WaitForGcToComplete blocked Alloc on HeapTrim for 25.144ms
I/adrive.appname(17087): Starting a blocking GC Alloc```
I added android:largeHeap="true" to AndroidManifest but it did not solve the problem.
I was using Unity IAP 2.0.3 before updating to Unity IAP 4.4.1. This problem did not occur in Unity IAP 2.0.3.(I apologize that I quote a very old version)
Are there any plans to reduce the memory used to fetch products in Unity IAP, or to allow FetchAdditionalProducts() to be called separately?
Thank you.
Sorry I’m not quite following. You are asking if it can be called separately, but you are explicitly calling the method in a loop. Why are you calling this method in OnInitialized, why not just retrieve all the products up front? Is that even slower? How many products do you have, are you testing with a tester with a large number of purchased products that would be a magnitude higher than actually expected?
I’m sorry that my explanation did not come across well.
Most importantly, when FetchAdditionalProducts() fetches products internally, does it expand all purchased Non-Consumable products into memory every time.
Why are you calling this method in OnInitialized, why not just retrieve all the products up front? Is that even slower?
You are correct partially. I set all StoreIDs at ConfigurationBuilder.AddProduct(), that “Unable to process purchase with transaction id” error no longer occurs and some accounts are faster.
But I am getting OutOfMemory Exceptions in the account that purchased very large quantities.
This occurs even when FetchAdditionalProducts() is called separately for all products. I believe that the most desirable thing is for FetchAdditionalProducts() to expand and execute in memory only for the additionalProducts it receives as arguments.
How many products do you have, are you testing with a tester with a large number of purchased products that would be a magnitude higher than actually expected?
Actually, there are a total of 700 Non-Consumable products in my app.
I tested on an account with 180 purchased products and got OutOfMemory Exceptions.(I tested with an account that purchased 700 products and the app crashed.)
This is very difficult, but I hope it can be resolved.
I got it! I found the cause of the problem.
Google’s products fetch calls many Android APIs in a loop within the IEnumerable method in Unity IAP.
When this Enumerable is evaluated, a large amount of data is expanded in java memory at the same time.
I found a solution for this.
Change GoogleQueryPurchasesService.QueryPurchasesWithSkuType() inside Unity IAP like this.
Task<IEnumerable<IGooglePurchase>> QueryPurchasesWithSkuType(string skuType)
{
var taskCompletion = new TaskCompletionSource<IEnumerable<IGooglePurchase>>();
m_BillingClient.QueryPurchasesAsync(skuType,
(billingResult, purchases) =>
{
// var result = IsResultOk(billingResult) ? m_PurchaseBuilder.BuildPurchases(purchases) : Enumerable.Empty<IGooglePurchase>();
var result = IsResultOk(billingResult) ? m_PurchaseBuilder.BuildPurchases(purchases).ToList() : new List<IGooglePurchase>();
taskCompletion.SetResult(result);
});
return taskCompletion.Task;
}
This will cause the OutOfMemory Exception to not occur because the Android GC works properly.
Is this fix problematic on Unity IAP? If not, please consider releasing this fix.