From 84567767a0cf970336cfa6e015837dd01bc21b53 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sat, 20 May 2023 15:47:47 +0300 Subject: [PATCH] Limit lifetime of memory content cache --- .../kotatsu/core/cache/DeferredLruCache.kt | 5 --- .../kotatsu/core/cache/ExpiringLruCache.kt | 33 ++++++++++++++++++ .../kotatsu/core/cache/ExpiringValue.kt | 34 +++++++++++++++++++ .../kotatsu/core/cache/MemoryContentCache.kt | 15 ++++---- 4 files changed, 75 insertions(+), 12 deletions(-) delete mode 100644 app/src/main/java/org/koitharu/kotatsu/core/cache/DeferredLruCache.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/core/cache/ExpiringLruCache.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/core/cache/ExpiringValue.kt diff --git a/app/src/main/java/org/koitharu/kotatsu/core/cache/DeferredLruCache.kt b/app/src/main/java/org/koitharu/kotatsu/core/cache/DeferredLruCache.kt deleted file mode 100644 index 8b9e08aa3..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/core/cache/DeferredLruCache.kt +++ /dev/null @@ -1,5 +0,0 @@ -package org.koitharu.kotatsu.core.cache - -import androidx.collection.LruCache - -class DeferredLruCache(maxSize: Int) : LruCache>(maxSize) diff --git a/app/src/main/java/org/koitharu/kotatsu/core/cache/ExpiringLruCache.kt b/app/src/main/java/org/koitharu/kotatsu/core/cache/ExpiringLruCache.kt new file mode 100644 index 000000000..34d46dfca --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/core/cache/ExpiringLruCache.kt @@ -0,0 +1,33 @@ +package org.koitharu.kotatsu.core.cache + +import androidx.collection.LruCache +import java.util.concurrent.TimeUnit + +class ExpiringLruCache( + val maxSize: Int, + private val lifetime: Long, + private val timeUnit: TimeUnit, +) { + + private val cache = LruCache>(maxSize) + + operator fun get(key: ContentCache.Key): T? { + val value = cache.get(key) ?: return null + if (value.isExpired) { + cache.remove(key) + } + return value.get() + } + + operator fun set(key: ContentCache.Key, value: T) { + cache.put(key, ExpiringValue(value, lifetime, timeUnit)) + } + + fun clear() { + cache.evictAll() + } + + fun trimToSize(size: Int) { + cache.trimToSize(size) + } +} diff --git a/app/src/main/java/org/koitharu/kotatsu/core/cache/ExpiringValue.kt b/app/src/main/java/org/koitharu/kotatsu/core/cache/ExpiringValue.kt new file mode 100644 index 000000000..2d561bb0c --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/core/cache/ExpiringValue.kt @@ -0,0 +1,34 @@ +package org.koitharu.kotatsu.core.cache + +import android.os.SystemClock +import java.util.concurrent.TimeUnit + +class ExpiringValue( + private val value: T, + lifetime: Long, + timeUnit: TimeUnit, +) { + + private val expiresAt = SystemClock.elapsedRealtime() + timeUnit.toMillis(lifetime) + + val isExpired: Boolean + get() = SystemClock.elapsedRealtime() >= expiresAt + + fun get(): T? = if (isExpired) null else value + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ExpiringValue<*> + + if (value != other.value) return false + return expiresAt == other.expiresAt + } + + override fun hashCode(): Int { + var result = value?.hashCode() ?: 0 + result = 31 * result + expiresAt.hashCode() + return result + } +} diff --git a/app/src/main/java/org/koitharu/kotatsu/core/cache/MemoryContentCache.kt b/app/src/main/java/org/koitharu/kotatsu/core/cache/MemoryContentCache.kt index ffa9a904e..722b06d41 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/cache/MemoryContentCache.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/cache/MemoryContentCache.kt @@ -6,6 +6,7 @@ import android.content.res.Configuration import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaPage import org.koitharu.kotatsu.parsers.model.MangaSource +import java.util.concurrent.TimeUnit class MemoryContentCache(application: Application) : ContentCache, ComponentCallbacks2 { @@ -13,8 +14,8 @@ class MemoryContentCache(application: Application) : ContentCache, ComponentCall application.registerComponentCallbacks(this) } - private val detailsCache = DeferredLruCache(4) - private val pagesCache = DeferredLruCache>(4) + private val detailsCache = ExpiringLruCache>(4, 5, TimeUnit.MINUTES) + private val pagesCache = ExpiringLruCache>>(4, 10, TimeUnit.MINUTES) override val isCachingEnabled: Boolean = true @@ -23,7 +24,7 @@ class MemoryContentCache(application: Application) : ContentCache, ComponentCall } override fun putDetails(source: MangaSource, url: String, details: SafeDeferred) { - detailsCache.put(ContentCache.Key(source, url), details) + detailsCache[ContentCache.Key(source, url)] = details } override suspend fun getPages(source: MangaSource, url: String): List? { @@ -31,7 +32,7 @@ class MemoryContentCache(application: Application) : ContentCache, ComponentCall } override fun putPages(source: MangaSource, url: String, pages: SafeDeferred>) { - pagesCache.put(ContentCache.Key(source, url), pages) + pagesCache[ContentCache.Key(source, url)] = pages } override fun onConfigurationChanged(newConfig: Configuration) = Unit @@ -43,17 +44,17 @@ class MemoryContentCache(application: Application) : ContentCache, ComponentCall trimCache(pagesCache, level) } - private fun trimCache(cache: DeferredLruCache<*>, level: Int) { + private fun trimCache(cache: ExpiringLruCache<*>, level: Int) { when (level) { ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL, ComponentCallbacks2.TRIM_MEMORY_COMPLETE, - ComponentCallbacks2.TRIM_MEMORY_MODERATE -> cache.evictAll() + ComponentCallbacks2.TRIM_MEMORY_MODERATE -> cache.clear() ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN, ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW, ComponentCallbacks2.TRIM_MEMORY_BACKGROUND -> cache.trimToSize(1) - else -> cache.trimToSize(cache.maxSize() / 2) + else -> cache.trimToSize(cache.maxSize / 2) } } }