From 779dd1f8fdcfc62d35f12ed585e266865097abb7 Mon Sep 17 00:00:00 2001 From: FooIbar <118464521+FooIbar@users.noreply.github.com> Date: Fri, 3 Oct 2025 22:54:06 +0800 Subject: [PATCH] Reduce DownloadManager initialization time --- .../ehviewer/download/DownloadManager.kt | 88 +++++++++++++------ .../ehviewer/ui/screen/DownloadsScreen.kt | 68 ++++++++------ 2 files changed, 101 insertions(+), 55 deletions(-) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/download/DownloadManager.kt b/app/src/main/kotlin/com/hippo/ehviewer/download/DownloadManager.kt index f8a8d4867..0907ef579 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/download/DownloadManager.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/download/DownloadManager.kt @@ -22,8 +22,11 @@ import androidx.compose.runtime.DisallowComposableCalls import androidx.compose.runtime.Stable import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.core.util.size import arrow.fx.coroutines.parMapNotNull import com.ehviewer.core.data.model.asEntity @@ -72,9 +75,7 @@ object DownloadManager : OnSpiderListener, CoroutineScope { override val coroutineContext = Dispatchers.IO + Job() // All download info list - private val allInfoList = runAssertingNotMainThread { - (EhDB.getAllDownloadInfo() as MutableList).apply { sortWith(comparator()) } - } + private val allInfoList = runAssertingNotMainThread { EhDB.getAllDownloadInfo() as MutableList } val downloadInfoList: List get() = allInfoList @@ -99,6 +100,21 @@ object DownloadManager : OnSpiderListener, CoroutineScope { private val mutex = Mutex() + private val sortMutex = Mutex() + private val sortMode + get() = SortMode.from(Settings.downloadSortMode.value) + + var isInitialized by mutableStateOf(false) + private set + + init { + launch { + val mode = sortMode + if (mode != SortMode.Default) sortDownloads(mode) + isInitialized = true + } + } + fun containLabel(label: String?): Boolean { if (label == null) { return false @@ -184,7 +200,9 @@ object DownloadManager : OnSpiderListener, CoroutineScope { info.label = label info.state = DownloadInfo.STATE_WAIT // Add to all download list and map - allInfoList.insertWith(info, comparator()) + sortMutex.withLock { + allInfoList.insertWith(info, sortMode.comparator()) + } mAllInfoMap[galleryInfo.gid] = info // Add to wait list @@ -241,10 +259,12 @@ object DownloadManager : OnSpiderListener, CoroutineScope { }.collectAsState(transform(info)).value suspend fun startAllDownload() { - val updateList = allInfoList.filter { - when (it.state) { - DownloadInfo.STATE_NONE, DownloadInfo.STATE_FAILED -> true - else -> false + val updateList = sortMutex.withLock { + allInfoList.filter { + when (it.state) { + DownloadInfo.STATE_NONE, DownloadInfo.STATE_FAILED -> true + else -> false + } } } if (updateList.isNotEmpty()) { @@ -258,8 +278,8 @@ object DownloadManager : OnSpiderListener, CoroutineScope { } } - suspend fun addDownload(downloadInfoList: List) { - val comparator = comparator() + suspend fun addDownload(downloadInfoList: List) = sortMutex.withLock { + val comparator = sortMode.comparator() downloadInfoList.forEach { info -> if (containDownloadInfo(info.gid)) return@forEach @@ -293,7 +313,9 @@ object DownloadManager : OnSpiderListener, CoroutineScope { info.state = DownloadInfo.STATE_NONE // Add to all download list and map - allInfoList.insertWith(info, comparator()) + sortMutex.withLock { + allInfoList.insertWith(info, sortMode.comparator()) + } mAllInfoMap[galleryInfo.gid] = info // Save to @@ -352,7 +374,9 @@ object DownloadManager : OnSpiderListener, CoroutineScope { EhDB.removeDownloadInfo(info) // Remove all list and map - allInfoList.remove(info) + sortMutex.withLock { + allInfoList.remove(info) + } mAllInfoMap.remove(info.gid) // Update listener @@ -375,7 +399,9 @@ object DownloadManager : OnSpiderListener, CoroutineScope { mAllInfoMap.remove(gid) } EhDB.removeDownloadInfo(list) - allInfoList.removeAll(list.toSet()) + sortMutex.withLock { + allInfoList.removeAll(list.toSet()) + } // Update listener list.forEach { mutableNotifyFlow.emit(it) } @@ -384,7 +410,7 @@ object DownloadManager : OnSpiderListener, CoroutineScope { ensureDownload() } - fun sortDownloads(mode: SortMode) { + suspend fun sortDownloads(mode: SortMode) = sortMutex.withLock { allInfoList.sortWith(mode.comparator()) } @@ -503,9 +529,11 @@ object DownloadManager : OnSpiderListener, CoroutineScope { val new = exist.copy(label = to) labelList.add(index, new) EhDB.updateDownloadLabel(new) - allInfoList.forEach { - if (it.label == from) { - it.label = to + sortMutex.withLock { + allInfoList.forEach { + if (it.label == from) { + it.label = to + } } } } @@ -521,21 +549,25 @@ object DownloadManager : OnSpiderListener, CoroutineScope { } removeAt(index) } - allInfoList.forEach { - if (it.label == label) { - it.label = null + sortMutex.withLock { + allInfoList.forEach { + if (it.label == label) { + it.label = null + } } } } suspend fun readMetadataFromLocal() { - val list = allInfoList.mapNotNull { - val updateGallery = it.pages == 0 || it.simpleTags == null - val updateArtist = it.artistInfoList.isEmpty() - if (updateGallery || updateArtist) { - Triple(it, updateGallery, updateArtist) - } else { - null + val list = sortMutex.withLock { + allInfoList.mapNotNull { + val updateGallery = it.pages == 0 || it.simpleTags == null + val updateArtist = it.artistInfoList.isEmpty() + if (updateGallery || updateArtist) { + Triple(it, updateGallery, updateArtist) + } else { + null + } } }.parMapNotNull(concurrency = 5) { (info, updateGallery, updateArtist) -> info.downloadDir?.run { @@ -785,8 +817,6 @@ object DownloadManager : OnSpiderListener, CoroutineScope { } private const val TAG = "DownloadManager" - - private fun comparator() = SortMode.from(Settings.downloadSortMode.value).comparator() } var downloadLocation: Path diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/DownloadsScreen.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/DownloadsScreen.kt index dca3b86d5..dbcaabca5 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/DownloadsScreen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/DownloadsScreen.kt @@ -5,8 +5,10 @@ import android.view.ViewConfiguration import androidx.compose.animation.AnimatedVisibilityScope import androidx.compose.animation.Crossfade import androidx.compose.animation.core.animateDpAsState +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row @@ -43,6 +45,7 @@ import androidx.compose.material.icons.filled.Reorder import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Shuffle import androidx.compose.material3.CardDefaults +import androidx.compose.material3.CircularWavyProgressIndicator import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.Icon @@ -93,7 +96,6 @@ import com.ehviewer.core.ui.component.LocalSideSheetState import com.ehviewer.core.ui.component.ProvideSideSheetContent import com.ehviewer.core.ui.icons.EhIcons import com.ehviewer.core.ui.icons.big.Download -import com.ehviewer.core.ui.util.Await import com.ehviewer.core.ui.util.HapticFeedbackType import com.ehviewer.core.ui.util.asyncState import com.ehviewer.core.ui.util.ifTrueThen @@ -113,7 +115,6 @@ import com.hippo.ehviewer.asMutableState import com.hippo.ehviewer.client.EhTagDatabase import com.hippo.ehviewer.collectAsState import com.hippo.ehviewer.download.DownloadManager -import com.hippo.ehviewer.download.DownloadManager.downloadInfoList import com.hippo.ehviewer.download.DownloadService import com.hippo.ehviewer.download.DownloadsFilterMode import com.hippo.ehviewer.download.SortMode @@ -148,6 +149,7 @@ fun AnimatedVisibilityScope.DownloadsScreen(navigator: DestinationsNavigator) = val filterMode by Settings.downloadFilterMode.collectAsState { DownloadsFilterMode.from(it) } var filterState by rememberSerializable { mutableStateOf(DownloadsFilterState(filterMode, Settings.recentDownloadLabel.value)) } var invalidateKey by rememberSaveable { mutableStateOf(false) } + var isLoading by rememberSaveable { mutableStateOf(true) } var searchBarExpanded by rememberSaveable { mutableStateOf(false) } var searchBarOffsetY by remember { mutableIntStateOf(0) } val animateItems by Settings.animateItems.collectAsState() @@ -183,10 +185,19 @@ fun AnimatedVisibilityScope.DownloadsScreen(navigator: DestinationsNavigator) = }, ) val hint = stringResource(R.string.search_bar_hint, title) - val list = remember(filterState, invalidateKey) { - downloadInfoList.filterTo(mutableStateListOf()) { info -> - filterState.take(info) + val list = if (DownloadManager.isInitialized) { + remember(filterState, invalidateKey) { + DownloadManager.downloadInfoList.filterTo(mutableStateListOf()) { info -> + filterState.take(info) + }.also { + launch { + delay(200) + isLoading = false + } + } } + } else { + remember { mutableStateListOf() } } val newLabel = stringResource(R.string.new_label_title) @@ -632,24 +643,26 @@ fun AnimatedVisibilityScope.DownloadsScreen(navigator: DestinationsNavigator) = } } - Await({ delay(200) }) { - if (list.isEmpty()) { - Column( - modifier = Modifier.padding(realPadding).fillMaxSize(), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Icon( - imageVector = EhIcons.Big.Default.Download, - contentDescription = null, - modifier = Modifier.padding(16.dp), - tint = MaterialTheme.colorScheme.primary, - ) - Text( - text = stringResource(id = R.string.no_download_info), - style = MaterialTheme.typography.headlineMedium, - ) - } + if (isLoading) { + Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.surface), contentAlignment = Alignment.Center) { + CircularWavyProgressIndicator() + } + } else if (list.isEmpty()) { + Column( + modifier = Modifier.padding(realPadding).fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Icon( + imageVector = EhIcons.Big.Default.Download, + contentDescription = null, + modifier = Modifier.padding(16.dp), + tint = MaterialTheme.colorScheme.primary, + ) + Text( + text = stringResource(id = R.string.no_download_info), + style = MaterialTheme.typography.headlineMedium, + ) } } } @@ -689,9 +702,12 @@ fun AnimatedVisibilityScope.DownloadsScreen(navigator: DestinationsNavigator) = oldMode.groupByDownloadLabel, ) val mode = SortMode.All[selected].copy(groupByDownloadLabel = checked) - DownloadManager.sortDownloads(mode) - sortMode = mode.flag - invalidateKey = !invalidateKey + if (mode != oldMode) { + sortMode = mode.flag + isLoading = true + DownloadManager.sortDownloads(mode) + invalidateKey = !invalidateKey + } } onClick(Icons.Default.FilterList) { val downloadStates = contextOf().resources.getStringArray(com.hippo.ehviewer.R.array.download_state).toList()