Fix memory leaks
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
20
app/src/main/java/org/koitharu/kotatsu/core/cache/SafeDeferred.kt
vendored
Normal file
20
app/src/main/java/org/koitharu/kotatsu/core/cache/SafeDeferred.kt
vendored
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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() }
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user