From a3263ab4afc77ee771bdc807baa73fedf4359c6b Mon Sep 17 00:00:00 2001 From: tarsin Date: Sun, 1 Jan 2023 16:58:34 +0800 Subject: [PATCH] Avoid unnecessary coroutine context switch --- .../java/com/hippo/ehviewer/EhApplication.kt | 70 +++++++++---------- .../hippo/ehviewer/client/EhTagDatabase.kt | 6 +- .../hippo/ehviewer/ui/scene/SearchBarScene.kt | 7 +- .../tachiyomi/ui/reader/ReaderActivity.kt | 51 +++++++------- .../util/lang/CoroutinesExtensions.kt | 60 ++++++++++++++++ 5 files changed, 122 insertions(+), 72 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/util/lang/CoroutinesExtensions.kt diff --git a/app/src/main/java/com/hippo/ehviewer/EhApplication.kt b/app/src/main/java/com/hippo/ehviewer/EhApplication.kt index efe729128..f79522ad8 100644 --- a/app/src/main/java/com/hippo/ehviewer/EhApplication.kt +++ b/app/src/main/java/com/hippo/ehviewer/EhApplication.kt @@ -58,12 +58,10 @@ import com.hippo.yorozuya.FileUtils import com.hippo.yorozuya.IntIdGenerator import eu.kanade.tachiyomi.core.preference.AndroidPreferenceStore import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences +import eu.kanade.tachiyomi.util.lang.launchIO import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import okhttp3.Cache import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.OkHttpClient @@ -118,36 +116,34 @@ class EhApplication : SceneApplication(), DefaultLifecycleObserver, ImageLoaderF AppCompatDelegate.setDefaultNightMode(Settings.getTheme()) - GlobalScope.launch { - withContext(Dispatchers.IO) { - try { - val downloadLocation = Settings.getDownloadLocation() - if (Settings.getMediaScan()) { - CommonOperations.removeNoMediaFile(downloadLocation) - } else { - CommonOperations.ensureNoMediaFile(downloadLocation) - } - } catch (t: Throwable) { - t.printStackTrace() - ExceptionUtils.throwIfFatal(t) + launchIO { + try { + val downloadLocation = Settings.getDownloadLocation() + if (Settings.getMediaScan()) { + CommonOperations.removeNoMediaFile(downloadLocation) + } else { + CommonOperations.ensureNoMediaFile(downloadLocation) } + } catch (t: Throwable) { + t.printStackTrace() + ExceptionUtils.throwIfFatal(t) + } - // Clear temp files - try { - clearTempDir() - } catch (t: Throwable) { - t.printStackTrace() - ExceptionUtils.throwIfFatal(t) - } + // Clear temp files + try { + clearTempDir() + } catch (t: Throwable) { + t.printStackTrace() + ExceptionUtils.throwIfFatal(t) } } - GlobalScope.launch { + launchIO { theDawnOfNewDay() } mIdGenerator.setNextId(Settings.getInt(KEY_GLOBAL_STUFF_NEXT_ID, 0)) } - private suspend fun theDawnOfNewDay() { + private fun theDawnOfNewDay() { if (!Settings.getRequestNews()) { return } @@ -159,22 +155,20 @@ class EhApplication : SceneApplication(), DefaultLifecycleObserver, ImageLoaderF EhCookieStore.KEY_IPD_PASS_HASH ) ) { - withContext(Dispatchers.IO) { - val referer = EhUrl.REFERER_E - val request = EhRequestBuilder(EhUrl.HOST_E + "news.php", referer).build() - val call = okHttpClient.newCall(request) - try { - call.execute().use { response -> - val responseBody = response.body - val body = responseBody.string() - val html = EventPaneParser.parse(body) - if (html != null) { - showEventPane(html) - } + val referer = EhUrl.REFERER_E + val request = EhRequestBuilder(EhUrl.HOST_E + "news.php", referer).build() + val call = okHttpClient.newCall(request) + try { + call.execute().use { response -> + val responseBody = response.body + val body = responseBody.string() + val html = EventPaneParser.parse(body) + if (html != null) { + showEventPane(html) } - } catch (e: Throwable) { - e.printStackTrace() } + } catch (e: Throwable) { + e.printStackTrace() } } } diff --git a/app/src/main/java/com/hippo/ehviewer/client/EhTagDatabase.kt b/app/src/main/java/com/hippo/ehviewer/client/EhTagDatabase.kt index 8b6c72300..0476a2328 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhTagDatabase.kt +++ b/app/src/main/java/com/hippo/ehviewer/client/EhTagDatabase.kt @@ -24,12 +24,10 @@ import com.hippo.util.ExceptionUtils import com.hippo.util.HashCodeUtils import com.hippo.yorozuya.FileUtils import com.hippo.yorozuya.IOUtils +import eu.kanade.tachiyomi.util.lang.launchIO import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.launch import okhttp3.OkHttpClient import okhttp3.Request import okio.BufferedSource @@ -193,7 +191,7 @@ object EhTagDatabase { @OptIn(DelicateCoroutinesApi::class) @JvmStatic fun update() { - GlobalScope.launch(Dispatchers.IO) { + launchIO { updateInternal() } } diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/SearchBarScene.kt b/app/src/main/java/com/hippo/ehviewer/ui/scene/SearchBarScene.kt index dbbc20858..c9047c392 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/SearchBarScene.kt +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/SearchBarScene.kt @@ -27,11 +27,10 @@ import com.hippo.ehviewer.Settings import com.hippo.ehviewer.client.EhTagDatabase import com.hippo.ehviewer.widget.SearchDatabase import com.hippo.scene.StageActivity -import kotlinx.coroutines.Dispatchers +import eu.kanade.tachiyomi.util.lang.withUIContext import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext abstract class SearchBarScene : ToolbarScene() { @@ -311,12 +310,12 @@ abstract class SearchBarScene : ToolbarScene() { @SuppressLint("NotifyDataSetChanged") private fun updateSuggestions(scrollToTop: Boolean = true) { - viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Default) { + viewLifecycleOwner.lifecycleScope.launch { val suggestions = mutableListOf() mergedSuggestionFlow().collect { suggestions.add(it) } - withContext(Dispatchers.Main) { + withUIContext { mSuggestionList = suggestions mSuggestionAdapter?.notifyDataSetChanged() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index 6b49e1e6b..993b30bde 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -85,6 +85,8 @@ import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsSheet import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType import eu.kanade.tachiyomi.ui.reader.viewer.BaseViewer import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer +import eu.kanade.tachiyomi.util.lang.launchIO +import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.preference.toggle import eu.kanade.tachiyomi.util.system.applySystemAnimatorScale import eu.kanade.tachiyomi.util.system.hasDisplayCutout @@ -93,15 +95,12 @@ import eu.kanade.tachiyomi.util.view.copy import eu.kanade.tachiyomi.util.view.popupMenu import eu.kanade.tachiyomi.util.view.setTooltip import eu.kanade.tachiyomi.widget.listener.SimpleAnimationListener -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.sample -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import java.io.File import java.io.FileInputStream import java.io.IOException @@ -134,29 +133,27 @@ class ReaderActivity : EhActivity() { AppConfig.getExternalTempDir().toString() + File.separator + mCacheFileName val cachefile = File(filepath) val resolver = contentResolver - lifecycleScope.launch { - withContext(Dispatchers.IO) { - var `is`: InputStream? = null - var os: OutputStream? = null - try { - `is` = FileInputStream(cachefile) - os = resolver.openOutputStream(uri) - IOUtils.copy(`is`, os) - } catch (e: IOException) { - e.printStackTrace() - } finally { - IOUtils.closeQuietly(`is`) - IOUtils.closeQuietly(os) - runOnUiThread { - Toast.makeText( - this@ReaderActivity, - getString(R.string.image_saved, uri.path), - Toast.LENGTH_SHORT - ).show() - } + lifecycleScope.launchIO { + var `is`: InputStream? = null + var os: OutputStream? = null + try { + `is` = FileInputStream(cachefile) + os = resolver.openOutputStream(uri) + IOUtils.copy(`is`, os) + } catch (e: IOException) { + e.printStackTrace() + } finally { + IOUtils.closeQuietly(`is`) + IOUtils.closeQuietly(os) + runOnUiThread { + Toast.makeText( + this@ReaderActivity, + getString(R.string.image_saved, uri.path), + Toast.LENGTH_SHORT + ).show() } - cachefile.delete() } + cachefile.delete() } } } @@ -253,7 +250,9 @@ class ReaderActivity : EhActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - window.colorMode = if (Image.isWideColorGamut && readerPreferences.wideColorGamut().get()) ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT else ActivityInfo.COLOR_MODE_DEFAULT + window.colorMode = if (Image.isWideColorGamut && readerPreferences.wideColorGamut() + .get() + ) ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT else ActivityInfo.COLOR_MODE_DEFAULT if (savedInstanceState == null) { onInit() } else { @@ -279,7 +278,7 @@ class ReaderActivity : EhActivity() { mCurrentIndex = if (mPage >= 0) mPage else mGalleryProvider!!.startPage } - lifecycleScope.launch(Dispatchers.Main) { + lifecycleScope.launchUI { mGalleryProvider!!.state.collect { if (it == PageLoader.STATE_READY) { setGallery() diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/lang/CoroutinesExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/lang/CoroutinesExtensions.kt new file mode 100644 index 000000000..41d697024 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/util/lang/CoroutinesExtensions.kt @@ -0,0 +1,60 @@ +package eu.kanade.tachiyomi.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) + +fun CoroutineScope.launchIO(block: suspend CoroutineScope.() -> Unit): Job = + launch(Dispatchers.IO, block = block) + +fun CoroutineScope.launchNonCancellable(block: suspend CoroutineScope.() -> Unit): Job = + launchIO { withContext(NonCancellable, block) } + +suspend fun withUIContext(block: suspend CoroutineScope.() -> T) = withContext(Dispatchers.Main, block) + +suspend fun withIOContext(block: suspend CoroutineScope.() -> T) = withContext(Dispatchers.IO, block) + +suspend fun withNonCancellableContext(block: suspend CoroutineScope.() -> T) = + withContext(NonCancellable, block)