Fix memory leaks

This commit is contained in:
Koitharu
2023-01-04 12:38:49 +02:00
parent bdb2ae9c2f
commit c03dcf6d2e
7 changed files with 54 additions and 36 deletions

View File

@@ -1,6 +1,5 @@
package org.koitharu.kotatsu.core.cache
import kotlinx.coroutines.Deferred
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.model.MangaSource
@@ -11,11 +10,11 @@ interface ContentCache {
suspend fun getDetails(source: MangaSource, url: String): Manga?
fun putDetails(source: MangaSource, url: String, details: Deferred<Manga>)
fun putDetails(source: MangaSource, url: String, details: SafeDeferred<Manga>)
suspend fun getPages(source: MangaSource, url: String): List<MangaPage>?
fun putPages(source: MangaSource, url: String, pages: Deferred<List<MangaPage>>)
fun putPages(source: MangaSource, url: String, pages: SafeDeferred<List<MangaPage>>)
data class Key(
val source: MangaSource,

View File

@@ -1,15 +1,14 @@
package org.koitharu.kotatsu.core.cache
import androidx.collection.LruCache
import kotlinx.coroutines.Deferred
class DeferredLruCache<T>(maxSize: Int) : LruCache<ContentCache.Key, Deferred<T>>(maxSize) {
class DeferredLruCache<T>(maxSize: Int) : LruCache<ContentCache.Key, SafeDeferred<T>>(maxSize) {
override fun entryRemoved(
evicted: Boolean,
key: ContentCache.Key,
oldValue: Deferred<T>,
newValue: Deferred<T>?,
oldValue: SafeDeferred<T>,
newValue: SafeDeferred<T>?,
) {
super.entryRemoved(evicted, key, oldValue, newValue)
oldValue.cancel()

View File

@@ -3,12 +3,10 @@ package org.koitharu.kotatsu.core.cache
import android.app.Application
import android.content.ComponentCallbacks2
import android.content.res.Configuration
import kotlinx.coroutines.Deferred
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.model.MangaSource
@Suppress("DeferredResultUnused")
class MemoryContentCache(application: Application) : ContentCache, ComponentCallbacks2 {
init {
@@ -21,18 +19,18 @@ class MemoryContentCache(application: Application) : ContentCache, ComponentCall
override val isCachingEnabled: Boolean = true
override suspend fun getDetails(source: MangaSource, url: String): Manga? {
return detailsCache[ContentCache.Key(source, url)]?.await()
return detailsCache[ContentCache.Key(source, url)]?.awaitOrNull()
}
override fun putDetails(source: MangaSource, url: String, details: Deferred<Manga>) {
override fun putDetails(source: MangaSource, url: String, details: SafeDeferred<Manga>) {
detailsCache.put(ContentCache.Key(source, url), details)
}
override suspend fun getPages(source: MangaSource, url: String): List<MangaPage>? {
return pagesCache[ContentCache.Key(source, url)]?.await()
return pagesCache[ContentCache.Key(source, url)]?.awaitOrNull()
}
override fun putPages(source: MangaSource, url: String, pages: Deferred<List<MangaPage>>) {
override fun putPages(source: MangaSource, url: String, pages: SafeDeferred<List<MangaPage>>) {
pagesCache.put(ContentCache.Key(source, url), pages)
}

View File

@@ -0,0 +1,20 @@
package org.koitharu.kotatsu.core.cache
import kotlinx.coroutines.Deferred
class SafeDeferred<T>(
private val delegate: Deferred<Result<T>>,
) {
suspend fun await(): T {
return delegate.await().getOrThrow()
}
suspend fun awaitOrNull(): T? {
return delegate.await().getOrNull()
}
fun cancel() {
delegate.cancel()
}
}

View File

@@ -1,6 +1,5 @@
package org.koitharu.kotatsu.core.cache
import kotlinx.coroutines.Deferred
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.model.MangaSource
@@ -11,9 +10,9 @@ class StubContentCache : ContentCache {
override suspend fun getDetails(source: MangaSource, url: String): Manga? = null
override fun putDetails(source: MangaSource, url: String, details: Deferred<Manga>) = Unit
override fun putDetails(source: MangaSource, url: String, details: SafeDeferred<Manga>) = Unit
override suspend fun getPages(source: MangaSource, url: String): List<MangaPage>? = null
override fun putPages(source: MangaSource, url: String, pages: Deferred<List<MangaPage>>) = Unit
override fun putPages(source: MangaSource, url: String, pages: SafeDeferred<List<MangaPage>>) = Unit
}

View File

@@ -1,8 +1,10 @@
package org.koitharu.kotatsu.core.parser
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import org.koitharu.kotatsu.core.cache.ContentCache
import org.koitharu.kotatsu.core.cache.SafeDeferred
import org.koitharu.kotatsu.core.prefs.SourceSettings
import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
@@ -14,6 +16,8 @@ import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.utils.ext.processLifecycleScope
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
class RemoteMangaRepository(
private val parser: MangaParser,
@@ -42,20 +46,20 @@ class RemoteMangaRepository(
override suspend fun getDetails(manga: Manga): Manga {
cache.getDetails(source, manga.url)?.let { return it }
return coroutineScope {
val details = async { parser.getDetails(manga) }
cache.putDetails(source, manga.url, details)
details
}.await()
val details = asyncSafe {
parser.getDetails(manga)
}
cache.putDetails(source, manga.url, details)
return details.await()
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
cache.getPages(source, chapter.url)?.let { return it }
return coroutineScope {
val pages = async { parser.getPages(chapter) }
cache.putPages(source, chapter.url, pages)
pages
}.await()
val pages = asyncSafe {
parser.getPages(chapter)
}
cache.putPages(source, chapter.url, pages)
return pages.await()
}
override suspend fun getPageUrl(page: MangaPage): String = parser.getPageUrl(page)
@@ -71,4 +75,10 @@ class RemoteMangaRepository(
}
private fun getConfig() = parser.config as SourceSettings
private fun <T> asyncSafe(block: suspend CoroutineScope.() -> T) = SafeDeferred(
processLifecycleScope.async(Dispatchers.Default) {
runCatchingCancellable { block() }
},
)
}

View File

@@ -4,9 +4,7 @@ import android.content.Context
import android.content.Intent
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.EntryPointAccessors
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import org.koitharu.kotatsu.base.ui.CoroutineIntentService
import org.koitharu.kotatsu.core.cache.ContentCache
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
@@ -16,7 +14,6 @@ import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.utils.ext.getParcelableExtraCompat
import org.koitharu.kotatsu.utils.ext.processLifecycleScope
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
import javax.inject.Inject
@@ -46,16 +43,12 @@ class MangaPrefetchService : CoroutineIntentService() {
private suspend fun prefetchDetails(manga: Manga) = coroutineScope {
val source = mangaRepositoryFactory.create(manga.source)
processLifecycleScope.launch(Dispatchers.Default) {
runCatchingCancellable { source.getDetails(manga) }
}.join()
runCatchingCancellable { source.getDetails(manga) }
}
private suspend fun prefetchPages(chapter: MangaChapter) {
val source = mangaRepositoryFactory.create(chapter.source)
processLifecycleScope.launch(Dispatchers.Default) {
runCatchingCancellable { source.getPages(chapter) }
}.join()
runCatchingCancellable { source.getPages(chapter) }
}
companion object {