diff --git a/gradle.properties b/gradle.properties index 7c77bf6..6a1e257 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ android.useAndroidX=true -libraryVersion=4.1.0 +libraryVersion=4.2.0 kotlinVersion=1.5.0 \ No newline at end of file diff --git a/src/main/java/com/statsig/androidsdk/Statsig.kt b/src/main/java/com/statsig/androidsdk/Statsig.kt index ae025ac..f1a696d 100644 --- a/src/main/java/com/statsig/androidsdk/Statsig.kt +++ b/src/main/java/com/statsig/androidsdk/Statsig.kt @@ -17,6 +17,7 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import java.util.* @@ -158,6 +159,7 @@ object Statsig { lifecycleListener = StatsigActivityLifecycleListener() application.registerActivityLifecycleCallbacks(lifecycleListener) logger = StatsigLogger( + statsigScope, sdkKey, options.api, statsigMetadata, @@ -331,16 +333,23 @@ object Statsig { pollForUpdates() } + @JvmSynthetic + suspend fun shutdownSuspend() { + enforceInitialized("shutdown") + pollingJob?.cancel() + logger.shutdown() + } + /** * Informs the Statsig SDK that the client is shutting down to complete cleanup saving state * @throws IllegalStateException if the SDK has not been initialized */ @JvmStatic fun shutdown() { - enforceInitialized("shutdown") - pollingJob?.cancel() - statsigScope.launch { - logger.flush() + runBlocking { + withContext(Dispatchers.Main.immediate) { + shutdownSuspend() + } } } @@ -435,7 +444,9 @@ object Statsig { override fun onActivityStopped(activity: Activity) { currentActivity = null - shutdown() + statsigScope.launch { + logger.flush() + } } override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) { diff --git a/src/main/java/com/statsig/androidsdk/StatsigLogger.kt b/src/main/java/com/statsig/androidsdk/StatsigLogger.kt index 835e6d1..874fc6d 100644 --- a/src/main/java/com/statsig/androidsdk/StatsigLogger.kt +++ b/src/main/java/com/statsig/androidsdk/StatsigLogger.kt @@ -1,13 +1,12 @@ package com.statsig.androidsdk import com.google.gson.Gson +import java.util.concurrent.Executors import android.content.SharedPreferences import com.google.gson.annotations.SerializedName -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay -import kotlinx.coroutines.withContext +import kotlinx.coroutines.* -internal const val MAX_EVENTS: Int = 500 +internal const val MAX_EVENTS: Int = 10 internal const val FLUSH_TIMER_MS: Long = 60000 internal const val CONFIG_EXPOSURE = "statsig::config_exposure" @@ -19,6 +18,7 @@ internal data class LogEventData( ) internal class StatsigLogger( + coroutineScope: CoroutineScope, private val sdkKey: String, private val api: String, private val statsigMetadata: StatsigMetadata, @@ -26,26 +26,29 @@ internal class StatsigLogger( ) { private val gson = Gson() - // Since these collections are not thread-safe, they will be modified in a single thread only + private val executor = Executors.newSingleThreadExecutor(); + private val singleThreadDispatcher = executor.asCoroutineDispatcher() + private val timer = coroutineScope.launch { + while (coroutineScope.isActive) { + delay(FLUSH_TIMER_MS) + flush() + } + } + // Modify in a single thread only internal var events = arrayListOf() suspend fun log(event: LogEvent) { - withContext(Dispatchers.Main.immediate) { // Run on main thread if not already in it + withContext(singleThreadDispatcher) { events.add(event) if (events.size >= MAX_EVENTS) { flush() } - - if (events.size == 1) { - delay(FLUSH_TIMER_MS) - flush() - } } } suspend fun flush() { - withContext(Dispatchers.Main.immediate) { + withContext(singleThreadDispatcher) { if (events.size == 0) { return@withContext } @@ -57,7 +60,7 @@ internal class StatsigLogger( suspend fun logGateExposure(gateName: String, gateValue: Boolean, ruleID: String, secondaryExposures: Array>, user: StatsigUser?) { - withContext(Dispatchers.Main.immediate) { + withContext(singleThreadDispatcher) { var event = LogEvent(GATE_EXPOSURE) event.user = user event.metadata = @@ -73,7 +76,7 @@ internal class StatsigLogger( suspend fun logConfigExposure(configName: String, ruleID: String, secondaryExposures: Array>, user: StatsigUser?) { - withContext(Dispatchers.Main.immediate) { + withContext(singleThreadDispatcher) { var event = LogEvent(CONFIG_EXPOSURE) event.user = user event.metadata = mapOf("config" to configName, "ruleID" to ruleID) @@ -81,4 +84,10 @@ internal class StatsigLogger( log(event) } } + + suspend fun shutdown() { + timer.cancel() + flush() + executor.shutdown() + } }