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 package org.koitharu.kotatsu.core.cache
import kotlinx.coroutines.Deferred
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaPage import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
@@ -11,11 +10,11 @@ interface ContentCache {
suspend fun getDetails(source: MangaSource, url: String): Manga? 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>? 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( data class Key(
val source: MangaSource, val source: MangaSource,

View File

@@ -1,15 +1,14 @@
package org.koitharu.kotatsu.core.cache package org.koitharu.kotatsu.core.cache
import androidx.collection.LruCache 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( override fun entryRemoved(
evicted: Boolean, evicted: Boolean,
key: ContentCache.Key, key: ContentCache.Key,
oldValue: Deferred<T>, oldValue: SafeDeferred<T>,
newValue: Deferred<T>?, newValue: SafeDeferred<T>?,
) { ) {
super.entryRemoved(evicted, key, oldValue, newValue) super.entryRemoved(evicted, key, oldValue, newValue)
oldValue.cancel() oldValue.cancel()

View File

@@ -3,12 +3,10 @@ package org.koitharu.kotatsu.core.cache
import android.app.Application import android.app.Application
import android.content.ComponentCallbacks2 import android.content.ComponentCallbacks2
import android.content.res.Configuration import android.content.res.Configuration
import kotlinx.coroutines.Deferred
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaPage import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
@Suppress("DeferredResultUnused")
class MemoryContentCache(application: Application) : ContentCache, ComponentCallbacks2 { class MemoryContentCache(application: Application) : ContentCache, ComponentCallbacks2 {
init { init {
@@ -21,18 +19,18 @@ class MemoryContentCache(application: Application) : ContentCache, ComponentCall
override val isCachingEnabled: Boolean = true override val isCachingEnabled: Boolean = true
override suspend fun getDetails(source: MangaSource, url: String): Manga? { 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) detailsCache.put(ContentCache.Key(source, url), details)
} }
override suspend fun getPages(source: MangaSource, url: String): List<MangaPage>? { 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) 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 package org.koitharu.kotatsu.core.cache
import kotlinx.coroutines.Deferred
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaPage import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.model.MangaSource 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 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 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 package org.koitharu.kotatsu.core.parser
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import org.koitharu.kotatsu.core.cache.ContentCache 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.core.prefs.SourceSettings
import org.koitharu.kotatsu.parsers.MangaParser import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider 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.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.model.SortOrder import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.utils.ext.processLifecycleScope
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
class RemoteMangaRepository( class RemoteMangaRepository(
private val parser: MangaParser, private val parser: MangaParser,
@@ -42,20 +46,20 @@ class RemoteMangaRepository(
override suspend fun getDetails(manga: Manga): Manga { override suspend fun getDetails(manga: Manga): Manga {
cache.getDetails(source, manga.url)?.let { return it } cache.getDetails(source, manga.url)?.let { return it }
return coroutineScope { val details = asyncSafe {
val details = async { parser.getDetails(manga) } parser.getDetails(manga)
cache.putDetails(source, manga.url, details) }
details cache.putDetails(source, manga.url, details)
}.await() return details.await()
} }
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> { override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
cache.getPages(source, chapter.url)?.let { return it } cache.getPages(source, chapter.url)?.let { return it }
return coroutineScope { val pages = asyncSafe {
val pages = async { parser.getPages(chapter) } parser.getPages(chapter)
cache.putPages(source, chapter.url, pages) }
pages cache.putPages(source, chapter.url, pages)
}.await() return pages.await()
} }
override suspend fun getPageUrl(page: MangaPage): String = parser.getPageUrl(page) 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 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 android.content.Intent
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.EntryPointAccessors import dagger.hilt.android.EntryPointAccessors
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import org.koitharu.kotatsu.base.ui.CoroutineIntentService import org.koitharu.kotatsu.base.ui.CoroutineIntentService
import org.koitharu.kotatsu.core.cache.ContentCache import org.koitharu.kotatsu.core.cache.ContentCache
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga 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.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.utils.ext.getParcelableExtraCompat import org.koitharu.kotatsu.utils.ext.getParcelableExtraCompat
import org.koitharu.kotatsu.utils.ext.processLifecycleScope
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
import javax.inject.Inject import javax.inject.Inject
@@ -46,16 +43,12 @@ class MangaPrefetchService : CoroutineIntentService() {
private suspend fun prefetchDetails(manga: Manga) = coroutineScope { private suspend fun prefetchDetails(manga: Manga) = coroutineScope {
val source = mangaRepositoryFactory.create(manga.source) val source = mangaRepositoryFactory.create(manga.source)
processLifecycleScope.launch(Dispatchers.Default) { runCatchingCancellable { source.getDetails(manga) }
runCatchingCancellable { source.getDetails(manga) }
}.join()
} }
private suspend fun prefetchPages(chapter: MangaChapter) { private suspend fun prefetchPages(chapter: MangaChapter) {
val source = mangaRepositoryFactory.create(chapter.source) val source = mangaRepositoryFactory.create(chapter.source)
processLifecycleScope.launch(Dispatchers.Default) { runCatchingCancellable { source.getPages(chapter) }
runCatchingCancellable { source.getPages(chapter) }
}.join()
} }
companion object { companion object {