-
Notifications
You must be signed in to change notification settings - Fork 50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Attempt to fix ANRs by moving some tasks during configure to background #1772
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,8 @@ import com.revenuecat.purchases.common.Backend | |
import com.revenuecat.purchases.common.BillingAbstract | ||
import com.revenuecat.purchases.common.Config | ||
import com.revenuecat.purchases.common.Constants | ||
import com.revenuecat.purchases.common.Delay | ||
import com.revenuecat.purchases.common.Dispatcher | ||
import com.revenuecat.purchases.common.LogIntent | ||
import com.revenuecat.purchases.common.PlatformInfo | ||
import com.revenuecat.purchases.common.ReceiptInfo | ||
|
@@ -73,7 +75,7 @@ import java.util.concurrent.atomic.AtomicBoolean | |
import kotlin.time.Duration.Companion.seconds | ||
|
||
@Suppress("LongParameterList", "LargeClass", "TooManyFunctions") | ||
internal class PurchasesOrchestrator constructor( | ||
internal class PurchasesOrchestrator( | ||
private val application: Application, | ||
backingFieldAppUserID: String?, | ||
private val backend: Backend, | ||
|
@@ -97,6 +99,7 @@ internal class PurchasesOrchestrator constructor( | |
private val purchasesStateCache: PurchasesStateCache, | ||
// This is nullable due to: https://github.com/RevenueCat/purchases-flutter/issues/408 | ||
private val mainHandler: Handler? = Handler(Looper.getMainLooper()), | ||
private val dispatcher: Dispatcher, | ||
) : LifecycleDelegate, CustomActivityLifecycleHandler { | ||
|
||
internal var state: PurchasesState | ||
|
@@ -194,22 +197,24 @@ internal class PurchasesOrchestrator constructor( | |
state = state.copy(appInBackground = false, firstTimeInForeground = false) | ||
} | ||
log(LogIntent.DEBUG, ConfigureStrings.APP_FOREGROUNDED) | ||
if (shouldRefreshCustomerInfo(firstTimeInForeground)) { | ||
log(LogIntent.DEBUG, CustomerInfoStrings.CUSTOMERINFO_STALE_UPDATING_FOREGROUND) | ||
customerInfoHelper.retrieveCustomerInfo( | ||
identityManager.currentAppUserID, | ||
fetchPolicy = CacheFetchPolicy.FETCH_CURRENT, | ||
appInBackground = false, | ||
allowSharingPlayStoreAccount = allowSharingPlayStoreAccount, | ||
) | ||
} | ||
offeringsManager.onAppForeground(identityManager.currentAppUserID) | ||
postPendingTransactionsHelper.syncPendingPurchaseQueue(allowSharingPlayStoreAccount) | ||
synchronizeSubscriberAttributesIfNeeded() | ||
offlineEntitlementsManager.updateProductEntitlementMappingCacheIfStale() | ||
flushPaywallEvents() | ||
if (firstTimeInForeground && isAndroidNOrNewer()) { | ||
diagnosticsSynchronizer?.syncDiagnosticsFileIfNeeded() | ||
enqueue { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think there should be any issue with enqueuing all this right? It is all pretty much updating caches, which is already changing into another thread to make the API call. This way we guarantee the accesses to the deviceCache are in the background There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup I think it should be mostly ok... My only concern is that there is a very small chance of the app moving to background before this is executed, so the code on |
||
if (shouldRefreshCustomerInfo(firstTimeInForeground)) { | ||
log(LogIntent.DEBUG, CustomerInfoStrings.CUSTOMERINFO_STALE_UPDATING_FOREGROUND) | ||
customerInfoHelper.retrieveCustomerInfo( | ||
identityManager.currentAppUserID, | ||
fetchPolicy = CacheFetchPolicy.FETCH_CURRENT, | ||
appInBackground = false, | ||
allowSharingPlayStoreAccount = allowSharingPlayStoreAccount, | ||
) | ||
} | ||
offeringsManager.onAppForeground(identityManager.currentAppUserID) | ||
postPendingTransactionsHelper.syncPendingPurchaseQueue(allowSharingPlayStoreAccount) | ||
synchronizeSubscriberAttributesIfNeeded() | ||
offlineEntitlementsManager.updateProductEntitlementMappingCacheIfStale() | ||
flushPaywallEvents() | ||
if (firstTimeInForeground && isAndroidNOrNewer()) { | ||
diagnosticsSynchronizer?.syncDiagnosticsFileIfNeeded() | ||
} | ||
} | ||
} | ||
|
||
|
@@ -775,6 +780,10 @@ internal class PurchasesOrchestrator constructor( | |
//endregion | ||
|
||
// region Private Methods | ||
@Synchronized | ||
vegaro marked this conversation as resolved.
Show resolved
Hide resolved
|
||
private fun enqueue(command: () -> Unit) { | ||
dispatcher.enqueue({ command() }, Delay.NONE) | ||
} | ||
|
||
private fun shouldRefreshCustomerInfo(firstTimeInForeground: Boolean): Boolean { | ||
return !appConfig.customEntitlementComputation && | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,13 @@ | ||
package com.revenuecat.purchases.identity | ||
|
||
import android.content.SharedPreferences | ||
import com.revenuecat.purchases.CustomerInfo | ||
import com.revenuecat.purchases.PurchasesError | ||
import com.revenuecat.purchases.PurchasesErrorCode | ||
import com.revenuecat.purchases.VerificationResult | ||
import com.revenuecat.purchases.common.Backend | ||
import com.revenuecat.purchases.common.Delay | ||
import com.revenuecat.purchases.common.Dispatcher | ||
import com.revenuecat.purchases.common.LogIntent | ||
import com.revenuecat.purchases.common.caching.DeviceCache | ||
import com.revenuecat.purchases.common.debugLog | ||
|
@@ -20,14 +23,15 @@ import com.revenuecat.purchases.subscriberattributes.caching.SubscriberAttribute | |
import java.util.Locale | ||
import java.util.UUID | ||
|
||
@Suppress("TooManyFunctions") | ||
@Suppress("TooManyFunctions", "LongParameterList") | ||
internal class IdentityManager( | ||
private val deviceCache: DeviceCache, | ||
private val subscriberAttributesCache: SubscriberAttributesCache, | ||
private val subscriberAttributesManager: SubscriberAttributesManager, | ||
private val offeringsCache: OfferingsCache, | ||
private val backend: Backend, | ||
private val offlineEntitlementsManager: OfflineEntitlementsManager, | ||
private val dispatcher: Dispatcher, | ||
) { | ||
|
||
val currentAppUserID: String | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could potentially fail now since setting the user id happens asynchronously now right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hmm right... actually... any call in this class right? for example I think we need to make all async and make sure they are all enqueued in the same dispatcher, right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup pretty much, which seems it would be a pretty big undertaking... |
||
|
@@ -38,7 +42,9 @@ internal class IdentityManager( | |
// region Public functions | ||
|
||
@Synchronized | ||
fun configure(appUserID: String?) { | ||
fun configure( | ||
appUserID: String?, | ||
) { | ||
if (appUserID?.isBlank() == true) { | ||
log(LogIntent.WARNING, IdentityStrings.EMPTY_APP_USER_ID_WILL_BECOME_ANONYMOUS) | ||
} | ||
|
@@ -49,10 +55,16 @@ internal class IdentityManager( | |
?: deviceCache.getLegacyCachedAppUserID() | ||
?: generateRandomID() | ||
log(LogIntent.USER, IdentityStrings.IDENTIFYING_APP_USER_ID.format(appUserIDToUse)) | ||
deviceCache.cacheAppUserID(appUserIDToUse) | ||
subscriberAttributesCache.cleanUpSubscriberAttributeCache(appUserIDToUse) | ||
deviceCache.cleanupOldAttributionData() | ||
invalidateCustomerInfoAndETagCacheIfNeeded(appUserIDToUse) | ||
|
||
val cacheEditor = deviceCache.startEditing() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This change (chaining the editions) will probably have marginal effects, but I don't think it hurts |
||
deviceCache.cacheAppUserID(appUserIDToUse, cacheEditor) | ||
subscriberAttributesCache.cleanUpSubscriberAttributeCache(appUserIDToUse, cacheEditor) | ||
invalidateCustomerInfoAndETagCacheIfNeeded(appUserIDToUse, cacheEditor) | ||
cacheEditor.apply() | ||
|
||
enqueue { | ||
deviceCache.cleanupOldAttributionData() | ||
tonidero marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
|
||
fun logIn( | ||
|
@@ -135,11 +147,17 @@ internal class IdentityManager( | |
} | ||
} | ||
|
||
private fun invalidateCustomerInfoAndETagCacheIfNeeded(appUserID: String) { | ||
private fun invalidateCustomerInfoAndETagCacheIfNeeded( | ||
appUserID: String, | ||
cacheEditor: SharedPreferences.Editor, | ||
) { | ||
if (backend.verificationMode == SignatureVerificationMode.Disabled) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess this is to avoid the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. exactly! I noticed we were getting the customer info all the time! I will add a comment here to make sure it's clear |
||
return | ||
} | ||
val cachedCustomerInfo = deviceCache.getCachedCustomerInfo(appUserID) | ||
if (shouldInvalidateCustomerInfoAndETagCache(cachedCustomerInfo)) { | ||
infoLog(IdentityStrings.INVALIDATING_CACHED_CUSTOMER_INFO) | ||
deviceCache.clearCustomerInfoCache(appUserID) | ||
deviceCache.clearCustomerInfoCache(appUserID, cacheEditor) | ||
backend.clearCaches() | ||
} | ||
} | ||
|
@@ -172,5 +190,10 @@ internal class IdentityManager( | |
backend.clearCaches() | ||
} | ||
|
||
@Synchronized | ||
private fun enqueue(command: () -> Unit) { | ||
dispatcher.enqueue({ command() }, Delay.NONE) | ||
} | ||
|
||
// endregion | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see this dispatcher was only used previously to parse offerings right? I think that should be ok then (as long as it's not the same dispatcher we run backend queries on)... I'm wondering if we should rename this, but it could come on a separate PR if needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree we should rename it. I had originally created a new backgroundTaskDispatcher before I noticed we could reuse this one. Is that naming good?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I think that could be enough as a "generic" dispatcher 👍