From 509eee5dfb81266ea7056587f695910506248a8a Mon Sep 17 00:00:00 2001 From: AntsyLich <59261191+AntsyLich@users.noreply.github.com> Date: Fri, 12 Jun 2026 17:14:29 +0600 Subject: [PATCH] Use app scoped CoroutineScope (#3403) --- .../tachiyomi/data/download/DownloadCache.kt | 9 ++--- .../data/download/DownloadManager.kt | 8 +++-- .../tachiyomi/data/download/Downloader.kt | 7 ++-- .../data/library/LibraryUpdateNotifier.kt | 6 ++-- .../data/notification/NotificationReceiver.kt | 29 ++++++++++++++-- .../java/eu/kanade/tachiyomi/di/AppModule.kt | 19 +++++++---- .../tachiyomi/extension/ExtensionManager.kt | 6 ++-- .../extension/util/ExtensionInstaller.kt | 6 ++-- .../tachiyomi/source/AndroidSourceManager.kt | 12 +++---- .../tachiyomi/ui/reader/ReaderViewModel.kt | 2 +- .../ui/reader/loader/ChapterLoader.kt | 4 ++- .../ui/reader/loader/HttpPageLoader.kt | 10 +++--- .../kanade/tachiyomi/network/NetworkHelper.kt | 4 ++- .../interceptor/CloudflareInterceptor.kt | 4 ++- .../network/interceptor/WebViewInterceptor.kt | 5 ++- .../common/util/lang/CoroutinesExtensions.kt | 33 ------------------- .../domain/storage/service/StorageManager.kt | 7 ++-- 17 files changed, 91 insertions(+), 80 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt index 920be6172..824053728 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt @@ -19,6 +19,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart @@ -53,6 +54,7 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.io.File import kotlin.time.Duration.Companion.hours +import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds /** @@ -63,17 +65,16 @@ import kotlin.time.Duration.Companion.seconds */ class DownloadCache( private val context: Context, + private val scope: CoroutineScope, private val provider: DownloadProvider = Injekt.get(), private val sourceManager: SourceManager = Injekt.get(), private val extensionManager: ExtensionManager = Injekt.get(), private val storageManager: StorageManager = Injekt.get(), ) { - - private val scope = CoroutineScope(Dispatchers.IO) - private val _changes: Channel = Channel(Channel.UNLIMITED) val changes = _changes.receiveAsFlow() .onStart { emit(Unit) } + .flowOn(Dispatchers.IO) .shareIn(scope, SharingStarted.Lazily, 1) /** @@ -101,7 +102,7 @@ class DownloadCache( init { // Attempt to read cache file - scope.launch { + scope.launchIO { rootDownloadsDirMutex.withLock { try { if (diskCacheFile.exists()) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt index 8255b22c8..3e7adc617 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt @@ -4,6 +4,7 @@ import android.content.Context import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.model.Page +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.drop @@ -35,6 +36,7 @@ import uy.kohesive.injekt.api.get */ class DownloadManager( private val context: Context, + private val scope: CoroutineScope, private val provider: DownloadProvider = Injekt.get(), private val cache: DownloadCache = Injekt.get(), private val getCategories: GetCategories = Injekt.get(), @@ -45,7 +47,7 @@ class DownloadManager( /** * Downloader whose only task is to download chapters. */ - private val downloader = Downloader(context, provider, cache) + private val downloader = Downloader(context, provider, cache, scope) val isRunning: Boolean get() = downloader.isRunning @@ -221,7 +223,7 @@ class DownloadManager( * @param source the source of the chapters. */ fun deleteChapters(chapters: List, manga: Manga, source: Source) { - launchIO { + scope.launchIO { val filteredChapters = getChaptersToDelete(chapters, manga) if (filteredChapters.isEmpty()) { return@launchIO @@ -248,7 +250,7 @@ class DownloadManager( * @param removeQueued whether to also remove queued downloads. */ fun deleteManga(manga: Manga, source: Source, removeQueued: Boolean = true) { - launchIO { + scope.launchIO { if (removeQueued) { downloader.removeFromQueue(manga) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt index 60134351a..6891715db 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt @@ -44,7 +44,6 @@ import okhttp3.Response import tachiyomi.core.common.i18n.stringResource import tachiyomi.core.common.storage.extension import tachiyomi.core.common.util.lang.launchIO -import tachiyomi.core.common.util.lang.launchNow import tachiyomi.core.common.util.lang.withIOContext import tachiyomi.core.common.util.system.ImageUtil import tachiyomi.core.common.util.system.logcat @@ -71,6 +70,7 @@ class Downloader( private val context: Context, private val provider: DownloadProvider, private val cache: DownloadCache, + private val scope: CoroutineScope, private val sourceManager: SourceManager = Injekt.get(), private val chapterCache: ChapterCache = Injekt.get(), private val downloadPreferences: DownloadPreferences = Injekt.get(), @@ -95,7 +95,6 @@ class Downloader( */ private val notifier by lazy { DownloadNotifier(context) } - private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) private var downloaderJob: Job? = null /** @@ -111,7 +110,7 @@ class Downloader( var isPaused: Boolean = false init { - launchNow { + scope.launch { val chapters = async { store.restore() } addAllToQueue(chapters.await()) } @@ -190,7 +189,7 @@ class Downloader( private fun launchDownloaderJob() { if (isRunning) return - downloaderJob = scope.launch { + downloaderJob = scope.launchIO { val activeDownloadsFlow = combine( queueState, downloadPreferences.parallelSourceLimit.changes(), diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt index f90ad1fca..d7c51e73c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt @@ -28,6 +28,8 @@ import eu.kanade.tachiyomi.util.system.cancelNotification import eu.kanade.tachiyomi.util.system.getBitmapOrNull import eu.kanade.tachiyomi.util.system.notificationBuilder import eu.kanade.tachiyomi.util.system.notify +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import tachiyomi.core.common.Constants import tachiyomi.core.common.i18n.pluralStringResource import tachiyomi.core.common.i18n.stringResource @@ -44,7 +46,7 @@ import java.text.NumberFormat class LibraryUpdateNotifier( private val context: Context, - + private val scope: CoroutineScope = Injekt.get(), private val securityPreferences: SecurityPreferences = Injekt.get(), private val sourceManager: SourceManager = Injekt.get(), ) { @@ -209,7 +211,7 @@ class LibraryUpdateNotifier( // Per-manga notification if (!securityPreferences.hideNotificationContent.get()) { - launchUI { + scope.launch { context.notify( updates.map { (manga, chapters) -> NotificationManagerCompat.NotificationWithIdAndTag( diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt index e23b3f78c..b1dd5c9b3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt @@ -6,6 +6,8 @@ import android.content.Context import android.content.Intent import android.net.Uri import androidx.core.net.toUri +import androidx.lifecycle.ProcessLifecycleOwner +import androidx.lifecycle.lifecycleScope import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.library.LibraryUpdateJob @@ -17,6 +19,10 @@ import eu.kanade.tachiyomi.util.system.getParcelableExtraCompat import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.toShareIntent import eu.kanade.tachiyomi.util.system.toast +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import tachiyomi.core.common.Constants import tachiyomi.core.common.util.lang.launchIO @@ -32,6 +38,8 @@ import tachiyomi.i18n.MR import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID /** @@ -45,6 +53,7 @@ class NotificationReceiver : BroadcastReceiver() { private val getChapter: GetChapter by injectLazy() private val updateChapter: UpdateChapter by injectLazy() private val downloadManager: DownloadManager by injectLazy() + private val scope: CoroutineScope by injectLazy() override fun onReceive(context: Context, intent: Intent) { when (intent.action) { @@ -197,7 +206,7 @@ class NotificationReceiver : BroadcastReceiver() { val downloadPreferences: DownloadPreferences = Injekt.get() val sourceManager: SourceManager = Injekt.get() - launchIO { + async(scope, Dispatchers.IO) { val toUpdate = chapterUrls.mapNotNull { getChapter.await(it, mangaId) } .map { val chapter = it.copy(read = true) @@ -223,13 +232,27 @@ class NotificationReceiver : BroadcastReceiver() { * @param mangaId id of manga */ private fun downloadChapters(chapterUrls: Array, mangaId: Long) { - launchIO { - val manga = getManga.await(mangaId) ?: return@launchIO + async(scope, Dispatchers.IO) { + val manga = getManga.await(mangaId) ?: return@async val chapters = chapterUrls.mapNotNull { getChapter.await(it, mangaId) } downloadManager.downloadChapters(manga, chapters) } } + private fun BroadcastReceiver.async( + scope: CoroutineScope, + context: CoroutineContext = EmptyCoroutineContext, + start: CoroutineStart = CoroutineStart.DEFAULT, + block: suspend CoroutineScope.() -> Unit, + ) { + val result = goAsync() + try { + scope.launch(context, start, block) + } finally { + result.finish() + } + } + companion object { private const val NAME = "NotificationReceiver" diff --git a/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt b/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt index 9828155df..4ae504e37 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt @@ -2,6 +2,8 @@ package eu.kanade.tachiyomi.di import android.app.Application import androidx.core.content.ContextCompat +import androidx.lifecycle.ProcessLifecycleOwner +import androidx.lifecycle.lifecycleScope import androidx.sqlite.driver.bundled.BundledSQLiteDriver import app.cash.sqldelight.db.SqlDriver import com.eygraber.sqldelight.androidx.driver.AndroidxSqliteConfiguration @@ -20,6 +22,9 @@ import eu.kanade.tachiyomi.extension.ExtensionManager import eu.kanade.tachiyomi.network.JavaScriptEngine import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.source.AndroidSourceManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.plus import kotlinx.serialization.json.Json import kotlinx.serialization.protobuf.ProtoBuf import nl.adaptivity.xmlutil.XmlDeclMode @@ -101,18 +106,20 @@ class AppModule(val app: Application) : InjektModule { ProtoBuf } + addSingletonFactory { ProcessLifecycleOwner.get().lifecycleScope + SupervisorJob() } + addSingletonFactory { ChapterCache(app, get()) } addSingletonFactory { CoverCache(app) } - addSingletonFactory { NetworkHelper(app, get()) } + addSingletonFactory { NetworkHelper(app, get(), get()) } addSingletonFactory { JavaScriptEngine(app) } - addSingletonFactory { AndroidSourceManager(app, get(), get()) } - addSingletonFactory { ExtensionManager(app) } + addSingletonFactory { AndroidSourceManager(app, get(), get(), get()) } + addSingletonFactory { ExtensionManager(app, get()) } addSingletonFactory { DownloadProvider(app) } - addSingletonFactory { DownloadManager(app) } - addSingletonFactory { DownloadCache(app) } + addSingletonFactory { DownloadManager(app, get()) } + addSingletonFactory { DownloadCache(app, get()) } addSingletonFactory { TrackerManager() } addSingletonFactory { DelayedTrackingStore(app) } @@ -122,7 +129,7 @@ class AppModule(val app: Application) : InjektModule { addSingletonFactory { AndroidStorageFolderProvider(app) } addSingletonFactory { LocalSourceFileSystem(get()) } addSingletonFactory { LocalCoverManager(app, get()) } - addSingletonFactory { StorageManager(app, get()) } + addSingletonFactory { StorageManager(app, get(), get()) } // Asynchronously init expensive components for a faster cold start ContextCompat.getMainExecutor(app).execute { diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt index 4f9856ccd..43314465d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt @@ -41,12 +41,10 @@ import java.util.Locale */ class ExtensionManager( private val context: Context, + private val scope: CoroutineScope, private val preferences: SourcePreferences = Injekt.get(), private val trustExtension: TrustExtension = Injekt.get(), ) { - - val scope = CoroutineScope(SupervisorJob()) - private val _isInitialized = MutableStateFlow(false) val isInitialized: StateFlow = _isInitialized.asStateFlow() @@ -58,7 +56,7 @@ class ExtensionManager( /** * The installer which installs, updates and uninstalls the extensions. */ - private val installer by lazy { ExtensionInstaller(context) } + private val installer by lazy { ExtensionInstaller(context, scope) } private val iconMap = mutableMapOf() diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstaller.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstaller.kt index 5eae3fe47..3b6590a28 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstaller.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstaller.kt @@ -22,6 +22,7 @@ import kotlinx.coroutines.launch import logcat.LogPriority import okhttp3.OkHttpClient import okhttp3.Request +import tachiyomi.core.common.util.lang.launchIO import tachiyomi.core.common.util.system.logcat import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -34,9 +35,8 @@ import java.io.File */ internal class ExtensionInstaller( private val context: Context, + private val scope: CoroutineScope, ) { - - private val scope = CoroutineScope(Dispatchers.IO) private val activeJobs = mutableMapOf() private val activeSteps = mutableMapOf>() private val extensionInstaller = Injekt.get().extensionInstaller @@ -57,7 +57,7 @@ internal class ExtensionInstaller( val step = MutableStateFlow(InstallStep.Pending) activeSteps[downloadId] = step - val job = scope.launch { + val job = scope.launchIO { val tmpFile = File(context.cacheDir, "extension_${extension.pkgName}.apk") try { step.value = InstallStep.Downloading diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/AndroidSourceManager.kt b/app/src/main/java/eu/kanade/tachiyomi/source/AndroidSourceManager.kt index 0fa40ba9d..928653401 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/AndroidSourceManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/AndroidSourceManager.kt @@ -15,6 +15,7 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking +import tachiyomi.core.common.util.lang.launchIO import tachiyomi.domain.source.model.StubSource import tachiyomi.domain.source.repository.StubSourceRepository import tachiyomi.domain.source.service.SourceManager @@ -26,6 +27,7 @@ import java.util.concurrent.ConcurrentHashMap class AndroidSourceManager( private val context: Context, + private val scope: CoroutineScope, private val extensionManager: ExtensionManager, private val sourceRepository: StubSourceRepository, ) : SourceManager { @@ -35,8 +37,6 @@ class AndroidSourceManager( private val downloadManager: DownloadManager by injectLazy() - private val scope = CoroutineScope(Job() + Dispatchers.IO) - private val sourcesMapFlow = MutableStateFlow(ConcurrentHashMap()) private val stubSourcesMap = ConcurrentHashMap() @@ -46,7 +46,7 @@ class AndroidSourceManager( } init { - scope.launch { + scope.launchIO { extensionManager.installedExtensionsFlow .collectLatest { extensions -> val mutableMap = ConcurrentHashMap( @@ -69,7 +69,7 @@ class AndroidSourceManager( } } - scope.launch { + scope.launchIO { sourceRepository.subscribeAll() .collectLatest { sources -> val mutableMap = stubSourcesMap.toMutableMap() @@ -100,9 +100,9 @@ class AndroidSourceManager( } private fun registerStubSource(source: StubSource) { - scope.launch { + scope.launchIO { val dbSource = sourceRepository.getStubSource(source.id) - if (dbSource == source) return@launch + if (dbSource == source) return@launchIO sourceRepository.upsertStubSource(source.id, source.lang, source.name) if (dbSource != null) { downloadManager.renameSource(dbSource, source) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt index 4c227636a..5bbf85878 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt @@ -286,7 +286,7 @@ class ReaderViewModel @JvmOverloads constructor( val context = Injekt.get() val source = sourceManager.getOrStub(manga.source) - loader = ChapterLoader(context, downloadManager, downloadProvider, manga, source) + loader = ChapterLoader(context, viewModelScope, downloadManager, downloadProvider, manga, source) loadChapter(loader!!, chapterList.first { chapterId == it.chapter.id }) Result.success(true) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt index 761c212a1..e091744a9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt @@ -6,6 +6,7 @@ import eu.kanade.tachiyomi.data.download.DownloadProvider import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter +import kotlinx.coroutines.CoroutineScope import mihon.core.archive.archiveReader import mihon.core.archive.epubReader import tachiyomi.core.common.i18n.stringResource @@ -22,6 +23,7 @@ import tachiyomi.source.local.io.Format */ class ChapterLoader( private val context: Context, + private val scope: CoroutineScope, private val downloadManager: DownloadManager, private val downloadProvider: DownloadProvider, private val manga: Manga, @@ -100,7 +102,7 @@ class ChapterLoader( is Format.Epub -> EpubPageLoader(format.file.epubReader(context)) } } - source is HttpSource -> HttpPageLoader(chapter, source) + source is HttpSource -> HttpPageLoader(chapter, source, scope) source is StubSource -> error(context.stringResource(MR.strings.source_not_installed, source.toString())) else -> error(context.stringResource(MR.strings.loader_not_implemented_error)) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt index 81530afa3..322340479 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt @@ -1,5 +1,6 @@ package eu.kanade.tachiyomi.ui.reader.loader +import android.provider.Settings import eu.kanade.tachiyomi.data.cache.ChapterCache import eu.kanade.tachiyomi.data.database.models.toDomainChapter import eu.kanade.tachiyomi.source.model.Page @@ -8,7 +9,9 @@ import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.filter @@ -31,11 +34,10 @@ import kotlin.math.min internal class HttpPageLoader( private val chapter: ReaderChapter, private val source: HttpSource, + scope: CoroutineScope, private val chapterCache: ChapterCache = Injekt.get(), ) : PageLoader() { - private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) - /** * A queue used to manage requests one by one while allowing priorities. */ @@ -124,14 +126,14 @@ internal class HttpPageLoader( queue.offer(PriorityPage(page, PriorityPage.RETRY)) } + @OptIn(DelicateCoroutinesApi::class) override fun recycle() { super.recycle() - scope.cancel() queue.clear() // Cache current page list progress for online chapters to allow a faster reopen chapter.pages?.let { pages -> - launchIO { + GlobalScope.launchIO { try { // Convert to pages without reader information val pagesToSave = pages.map { Page(it.index, it.url, it.imageUrl) } diff --git a/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt b/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt index 202c39063..c9ad60af8 100644 --- a/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt +++ b/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt @@ -5,6 +5,7 @@ import eu.kanade.tachiyomi.network.interceptor.CloudflareInterceptor import eu.kanade.tachiyomi.network.interceptor.IgnoreGzipInterceptor import eu.kanade.tachiyomi.network.interceptor.UncaughtExceptionInterceptor import eu.kanade.tachiyomi.network.interceptor.UserAgentInterceptor +import kotlinx.coroutines.CoroutineScope import okhttp3.Cache import okhttp3.OkHttpClient import okhttp3.brotli.BrotliInterceptor @@ -15,6 +16,7 @@ import java.util.concurrent.TimeUnit class NetworkHelper( private val context: Context, private val preferences: NetworkPreferences, + scope: CoroutineScope, ) { val cookieJar = AndroidCookieJar() @@ -64,7 +66,7 @@ class NetworkHelper( val client = clientBuilder .addInterceptor( - CloudflareInterceptor(context, cookieJar, ::defaultUserAgentProvider), + CloudflareInterceptor(context, cookieJar, scope, ::defaultUserAgentProvider), ) .build() diff --git a/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt b/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt index 517679aca..b55209550 100644 --- a/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt +++ b/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt @@ -11,6 +11,7 @@ import androidx.core.content.ContextCompat import eu.kanade.tachiyomi.network.AndroidCookieJar import eu.kanade.tachiyomi.util.system.isOutdated import eu.kanade.tachiyomi.util.system.toast +import kotlinx.coroutines.CoroutineScope import okhttp3.Cookie import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.Interceptor @@ -25,8 +26,9 @@ import java.util.concurrent.CountDownLatch class CloudflareInterceptor( private val context: Context, private val cookieManager: AndroidCookieJar, + scope: CoroutineScope, defaultUserAgentProvider: () -> String, -) : WebViewInterceptor(context, defaultUserAgentProvider) { +) : WebViewInterceptor(context, scope, defaultUserAgentProvider) { private val executor = ContextCompat.getMainExecutor(context) diff --git a/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt b/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt index 1223d51a4..c59e09ad6 100644 --- a/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt +++ b/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt @@ -9,6 +9,8 @@ import eu.kanade.tachiyomi.util.system.DeviceUtil import eu.kanade.tachiyomi.util.system.WebViewUtil import eu.kanade.tachiyomi.util.system.setDefaultSettings import eu.kanade.tachiyomi.util.system.toast +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import okhttp3.Headers import okhttp3.Interceptor import okhttp3.Request @@ -21,6 +23,7 @@ import java.util.concurrent.TimeUnit abstract class WebViewInterceptor( private val context: Context, + private val scope: CoroutineScope, private val defaultUserAgentProvider: () -> String, ) : Interceptor { @@ -56,7 +59,7 @@ abstract class WebViewInterceptor( } if (!WebViewUtil.supportsWebView(context)) { - launchUI { + scope.launch { context.toast(MR.strings.information_webview_required, Toast.LENGTH_LONG) } return response diff --git a/core/common/src/main/kotlin/tachiyomi/core/common/util/lang/CoroutinesExtensions.kt b/core/common/src/main/kotlin/tachiyomi/core/common/util/lang/CoroutinesExtensions.kt index a76bff2cc..89dff3b9b 100644 --- a/core/common/src/main/kotlin/tachiyomi/core/common/util/lang/CoroutinesExtensions.kt +++ b/core/common/src/main/kotlin/tachiyomi/core/common/util/lang/CoroutinesExtensions.kt @@ -10,39 +10,6 @@ import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -/** - * Think twice before using this. This is a delicate API. It is easy to accidentally create resource or memory leaks when GlobalScope is used. - * - * **Possible replacements** - * - suspend function - * - custom scope like view or presenter scope - */ -@DelicateCoroutinesApi -fun launchUI(block: suspend CoroutineScope.() -> Unit): Job = - GlobalScope.launch(Dispatchers.Main, CoroutineStart.DEFAULT, block) - -/** - * Think twice before using this. This is a delicate API. It is easy to accidentally create resource or memory leaks when GlobalScope is used. - * - * **Possible replacements** - * - suspend function - * - custom scope like view or presenter scope - */ -@DelicateCoroutinesApi -fun launchIO(block: suspend CoroutineScope.() -> Unit): Job = - GlobalScope.launch(Dispatchers.IO, CoroutineStart.DEFAULT, block) - -/** - * Think twice before using this. This is a delicate API. It is easy to accidentally create resource or memory leaks when GlobalScope is used. - * - * **Possible replacements** - * - suspend function - * - custom scope like view or presenter scope - */ -@DelicateCoroutinesApi -fun launchNow(block: suspend CoroutineScope.() -> Unit): Job = - GlobalScope.launch(Dispatchers.Main, CoroutineStart.UNDISPATCHED, block) - fun CoroutineScope.launchUI(block: suspend CoroutineScope.() -> Unit): Job = launch(Dispatchers.Main, block = block) diff --git a/domain/src/main/java/tachiyomi/domain/storage/service/StorageManager.kt b/domain/src/main/java/tachiyomi/domain/storage/service/StorageManager.kt index 002d976fb..7a2624096 100644 --- a/domain/src/main/java/tachiyomi/domain/storage/service/StorageManager.kt +++ b/domain/src/main/java/tachiyomi/domain/storage/service/StorageManager.kt @@ -10,6 +10,7 @@ import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.drop +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.receiveAsFlow @@ -17,15 +18,14 @@ import kotlinx.coroutines.flow.shareIn class StorageManager( private val context: Context, + scope: CoroutineScope, storagePreferences: StoragePreferences, ) { - - private val scope = CoroutineScope(Dispatchers.IO) - private var baseDir: UniFile? = getBaseDir(storagePreferences.baseStorageDirectory.get()) private val _changes: Channel = Channel(Channel.UNLIMITED) val changes = _changes.receiveAsFlow() + .flowOn(Dispatchers.IO) .shareIn(scope, SharingStarted.Lazily, 1) init { @@ -43,6 +43,7 @@ class StorageManager( } _changes.send(Unit) } + .flowOn(Dispatchers.IO) .launchIn(scope) }