mirror of
https://github.com/mihonapp/mihon.git
synced 2026-10-16 13:48:09 -07:00
Use an app scoped CoroutineScope
Replaces the eight ad hoc application lifetime scopes and the GlobalScope launchUI/launchIO/launchNow helpers with a single injected CoroutineScope(SupervisorJob() + Dispatchers.IO), qualified by @AppCoroutineScope. Every scope this replaces was already on IO, or named IO explicitly, so call sites keep their dispatcher. The two that need the main thread use scope.launchUI. Scopes with a real cancellation point keep their own: HttpPageLoader, ShizukuInstaller and the reader viewers. Only HttpPageLoader's page list cache save moves to the app scope, since it runs after recycle() has cancelled the loader's own scope. NotificationReceiver now holds the broadcast open with goAsync() while its work runs, so the process isn't torn down mid update. This reverts the revert of #3403. It was reverted for #3429, where restore() dropped every download because SourceManager.get() returned null until the extensions had loaded. That surface suspends since #3869, so the race is gone.
This commit is contained in:
@@ -7,6 +7,8 @@ import dev.zacsweers.metro.SingleIn
|
||||
import eu.kanade.tachiyomi.network.interceptor.CloudflareInterceptor
|
||||
import eu.kanade.tachiyomi.network.interceptor.UncaughtExceptionInterceptor
|
||||
import eu.kanade.tachiyomi.network.interceptor.UserAgentInterceptor
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import mihon.core.metro.AppCoroutineScope
|
||||
import okhttp3.Cache
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
@@ -17,6 +19,7 @@ import kotlin.time.Duration.Companion.seconds
|
||||
@Inject
|
||||
@SingleIn(AppScope::class)
|
||||
class NetworkHelper(
|
||||
@AppCoroutineScope private val scope: CoroutineScope,
|
||||
private val context: Context,
|
||||
private val preferences: NetworkPreferences,
|
||||
) {
|
||||
@@ -64,7 +67,7 @@ class NetworkHelper(
|
||||
|
||||
val client = clientBuilder
|
||||
.addInterceptor(
|
||||
CloudflareInterceptor(context, cookieJar, ::defaultUserAgentProvider),
|
||||
CloudflareInterceptor(context, cookieJar, scope, ::defaultUserAgentProvider),
|
||||
)
|
||||
.build()
|
||||
|
||||
|
||||
+3
-1
@@ -12,6 +12,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)
|
||||
|
||||
|
||||
+3
-1
@@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.util.system.WebViewUtil
|
||||
import eu.kanade.tachiyomi.util.system.setDefaultSettings
|
||||
import eu.kanade.tachiyomi.util.system.setUserAgent
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import okhttp3.Headers
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Request
|
||||
@@ -22,6 +23,7 @@ import java.util.concurrent.TimeUnit
|
||||
|
||||
abstract class WebViewInterceptor(
|
||||
private val context: Context,
|
||||
private val scope: CoroutineScope,
|
||||
private val defaultUserAgentProvider: () -> String,
|
||||
) : Interceptor {
|
||||
|
||||
@@ -57,7 +59,7 @@ abstract class WebViewInterceptor(
|
||||
}
|
||||
|
||||
if (!WebViewUtil.supportsWebView(context)) {
|
||||
launchUI {
|
||||
scope.launchUI {
|
||||
context.toast(MR.strings.information_webview_required, Toast.LENGTH_LONG)
|
||||
}
|
||||
return response
|
||||
|
||||
@@ -1,48 +1,12 @@
|
||||
package tachiyomi.core.common.util.lang
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.CoroutineStart
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.Job
|
||||
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)
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package mihon.core.metro
|
||||
|
||||
import dev.zacsweers.metro.Qualifier
|
||||
|
||||
/**
|
||||
* Qualifies the single application wide [kotlinx.coroutines.CoroutineScope].
|
||||
*
|
||||
* It lives for as long as the process does and is backed by a [kotlinx.coroutines.SupervisorJob] on
|
||||
* [kotlinx.coroutines.Dispatchers.IO], so a failure in one child never takes the others down. Use it for work that is
|
||||
* owned by an application scoped component and must outlive any screen. Work that belongs to a screen, a view model or
|
||||
* another cancellable lifecycle keeps its own scope.
|
||||
*/
|
||||
@Qualifier
|
||||
@Target(
|
||||
AnnotationTarget.VALUE_PARAMETER,
|
||||
AnnotationTarget.FUNCTION,
|
||||
AnnotationTarget.PROPERTY,
|
||||
AnnotationTarget.PROPERTY_GETTER,
|
||||
)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class AppCoroutineScope
|
||||
Reference in New Issue
Block a user