All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.com.seiko.imageloader.cache.memory.WeakMemoryCache.kt Maven / Gradle / Ivy

There is a newer version: 1.9.0
Show newest version
package com.seiko.imageloader.cache.memory

import androidx.compose.ui.graphics.painter.Painter
import com.seiko.imageloader.Image
import com.seiko.imageloader.identityHashCode
import com.seiko.imageloader.util.WeakReference
import com.seiko.imageloader.util.firstNotNullOfOrNullIndices
import com.seiko.imageloader.util.removeIfIndices
import kotlin.jvm.Synchronized

/**
 * An in-memory cache that holds weak references to [Painter]s.
 *
 * Bitmaps are added to [WeakMemoryCache] when they're removed from [StrongMemoryCache].
 */
internal interface WeakMemoryCache {
    val keys: Set
    fun get(key: MemoryKey): MemoryValue?
    fun set(key: MemoryKey, image: Image, extras: Map, size: Int)
    fun remove(key: MemoryKey): Boolean
    fun clearMemory()
}

/** A [WeakMemoryCache] implementation that holds no references. */
internal object EmptyWeakMemoryCache : WeakMemoryCache {
    override val keys get(): Set = emptySet()
    override fun get(key: MemoryKey): MemoryValue? = null
    override fun set(key: MemoryKey, image: Image, extras: Map, size: Int) = Unit
    override fun remove(key: MemoryKey) = false
    override fun clearMemory() = Unit
}

/** A [WeakMemoryCache] implementation backed by a [LinkedHashMap]. */
internal class RealWeakMemoryCache : WeakMemoryCache {

    internal val cache = LinkedHashMap>()
    private var operationsSinceCleanUp = 0

    override val keys: Set
        @Synchronized
        get() = cache.keys.toSet()

    @Synchronized
    override fun get(key: MemoryKey): MemoryValue? {
        val values = cache[key] ?: return null

        // Find the first bitmap that hasn't been collected.
        val value = values.firstNotNullOfOrNullIndices { value ->
            value.image.get()
        }

        cleanUpIfNecessary()
        return value
    }

    @Synchronized
    override fun set(key: MemoryKey, image: Image, extras: Map, size: Int) {
        val values = cache.getOrPut(key) { arrayListOf() }

        // Insert the value into the list sorted descending by size.
        run {
            val identityHashCode = image.identityHashCode
            val newValue = InternalValue(identityHashCode, WeakReference(image), extras, size)
            for (index in values.indices) {
                val value = values[index]
                if (size >= value.size) {
                    if (value.identityHashCode == identityHashCode && value.image.get() === image) {
                        values[index] = newValue
                    } else {
                        values.add(index, newValue)
                    }
                    return@run
                }
            }
            values += newValue
        }

        cleanUpIfNecessary()
    }

    @Synchronized
    override fun remove(key: MemoryKey): Boolean {
        return cache.remove(key) != null
    }

    @Synchronized
    override fun clearMemory() {
        operationsSinceCleanUp = 0
        cache.clear()
    }

    private fun cleanUpIfNecessary() {
        if (operationsSinceCleanUp++ >= CLEAN_UP_INTERVAL) {
            cleanUp()
        }
    }

    /** Remove any dereferenced bitmaps from the cache. */
    internal fun cleanUp() {
        operationsSinceCleanUp = 0

        // Remove all the values whose references have been collected.
        val iterator = cache.values.iterator()
        while (iterator.hasNext()) {
            val list = iterator.next()

            if (list.count() <= 1) {
                // Typically, the list will only contain 1 item. Handle this case in an optimal way here.
                if (list.firstOrNull()?.image?.get() == null) {
                    iterator.remove()
                }
            } else {
                // Iterate over the list of values and delete individual entries that have been collected.
                list.removeIfIndices { it.image.get() == null }

                if (list.isEmpty()) {
                    iterator.remove()
                }
            }
        }
    }

    internal class InternalValue(
        val identityHashCode: Int,
        val image: WeakReference,
        val extras: Map,
        val size: Int
    )

    companion object {
        private const val CLEAN_UP_INTERVAL = 10
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy