From 486f4e85db415c4c9c63184c9cd963929f727e6f Mon Sep 17 00:00:00 2001 From: theov Date: Sat, 16 May 2026 03:49:29 -0300 Subject: [PATCH] refactor: optimize album color scheme generation and caching Improves the efficiency of `ThemeStateHolder` by allowing multiple subscribers to wait for the same color scheme generation request. Also increases the LRU cache capacity to improve performance when navigating through large libraries. - Replaced the simple `pendingAlbumColorSchemeRequests` set with a synchronized `pendingAlbumColorSchemeTargets` map to track and update multiple `MutableStateFlow` instances for a single URI. - Increased `individualAlbumColorSchemes` LRU cache size from 30 to 96 entries. - Added validation to handle blank URI strings, returning a static `emptyAlbumColorScheme` flow. - Improved thread safety by using a dedicated lock for pending request operations and updated `onTrimMemory` accordingly. - Cleaned up unused `ConcurrentHashMap` import. --- .../viewmodel/ThemeStateHolder.kt | 49 +++++++++++++++---- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/viewmodel/ThemeStateHolder.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/viewmodel/ThemeStateHolder.kt index 6ece7b7fa..2846195ad 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/viewmodel/ThemeStateHolder.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/viewmodel/ThemeStateHolder.kt @@ -22,7 +22,6 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import javax.inject.Inject import javax.inject.Singleton -import java.util.concurrent.ConcurrentHashMap @Singleton class ThemeStateHolder @Inject constructor( @@ -139,30 +138,56 @@ class ThemeStateHolder @Inject constructor( 32, 0.75f, true ) { override fun removeEldestEntry(eldest: MutableMap.MutableEntry>?): Boolean { - return size > 30 + return size > 96 } } - private val pendingAlbumColorSchemeRequests = ConcurrentHashMap.newKeySet() + private val emptyAlbumColorScheme = MutableStateFlow(null).asStateFlow() + private val pendingAlbumColorSchemeLock = Any() + private val pendingAlbumColorSchemeTargets = mutableMapOf>>() private fun requestAlbumColorSchemeGeneration( uriString: String, targetFlow: MutableStateFlow ) { - if (!pendingAlbumColorSchemeRequests.add(uriString)) return + if (uriString.isBlank()) return + + val shouldStartRequest = synchronized(pendingAlbumColorSchemeLock) { + val existingTargets = pendingAlbumColorSchemeTargets[uriString] + if (existingTargets != null) { + existingTargets.add(targetFlow) + false + } else { + pendingAlbumColorSchemeTargets[uriString] = mutableSetOf(targetFlow) + true + } + } + + if (!shouldStartRequest) return + + val requestScope = scope + if (requestScope == null) { + synchronized(pendingAlbumColorSchemeLock) { + pendingAlbumColorSchemeTargets.remove(uriString) + } + return + } - scope?.launch(Dispatchers.IO) { + requestScope.launch(Dispatchers.IO) { + var scheme: ColorSchemePair? = null try { - val scheme = colorSchemeProcessor.getOrGenerateColorScheme( + scheme = colorSchemeProcessor.getOrGenerateColorScheme( albumArtUri = uriString, paletteStyle = currentPaletteStyle, colorAccuracyLevel = currentPaletteAccuracy ) - targetFlow.value = scheme } catch (_: Exception) { // Ignore or log } finally { - pendingAlbumColorSchemeRequests.remove(uriString) + val targets = synchronized(pendingAlbumColorSchemeLock) { + pendingAlbumColorSchemeTargets.remove(uriString)?.toList().orEmpty() + } + targets.forEach { it.value = scheme } } } } @@ -171,6 +196,8 @@ class ThemeStateHolder @Inject constructor( uriString: String, eager: Boolean = true ): StateFlow { + if (uriString.isBlank()) return emptyAlbumColorScheme + val existingFlow = individualAlbumColorSchemes[uriString] if (existingFlow != null) { if (eager && existingFlow.value == null) { @@ -190,6 +217,8 @@ class ThemeStateHolder @Inject constructor( } fun ensureAlbumColorScheme(uriString: String) { + if (uriString.isBlank()) return + val targetFlow = individualAlbumColorSchemes[uriString] ?: MutableStateFlow(null).also { individualAlbumColorSchemes[uriString] = it } @@ -276,7 +305,9 @@ class ThemeStateHolder @Inject constructor( level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL || level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE ) { - pendingAlbumColorSchemeRequests.clear() + synchronized(pendingAlbumColorSchemeLock) { + pendingAlbumColorSchemeTargets.clear() + } } }