diff --git a/app/src/main/java/io/legado/app/model/CacheBookModel.kt b/app/src/main/java/io/legado/app/model/CacheBookModel.kt index e58f3d49c..345092665 100644 --- a/app/src/main/java/io/legado/app/model/CacheBookModel.kt +++ b/app/src/main/java/io/legado/app/model/CacheBookModel.kt @@ -63,6 +63,7 @@ class CacheBookModel( private var isLoading = false private var isPaused = false + @Synchronized private fun notifyDownloadSetChanged() { host.emitDownloadingIndices(book.bookUrl, onDownloadSet.toSet()) if (host.cacheBookMap[book.bookUrl] === this) { @@ -75,6 +76,7 @@ class CacheBookModel( } } + @Synchronized private fun notifyErrorChanged() { val errors = host.errorIndices(book.bookUrl) host.emitDownloadError(book.bookUrl, errors) @@ -260,41 +262,43 @@ class CacheBookModel( addDownload(index, index) } - @Synchronized private fun onSuccess(chapter: BookChapter) { - onDownloadSet.remove(chapter.index) - chapterTasks.remove(chapter.index) - host.incrementSuccessCount() - retryCountMap.remove(chapter.index) - host.stateStore.markSuccess(book.bookUrl, chapter.index) + synchronized(this) { + onDownloadSet.remove(chapter.index) + chapterTasks.remove(chapter.index) + host.incrementSuccessCount() + retryCountMap.remove(chapter.index) + host.stateStore.markSuccess(book.bookUrl, chapter.index) + } notifyDownloadSetChanged() notifyErrorChanged() host.emitChapterCached(chapter) } - @Synchronized private fun onPreError(chapter: BookChapter, error: Throwable) { - waitingRetry = true - if (error !is ConcurrentException) { - retryCountMap[chapter.index] = (retryCountMap[chapter.index] ?: 0) + 1 - host.stateStore.markFailed(book.bookUrl, chapter.index) + synchronized(this) { + waitingRetry = true + if (error !is ConcurrentException) { + retryCountMap[chapter.index] = (retryCountMap[chapter.index] ?: 0) + 1 + host.stateStore.markFailed(book.bookUrl, chapter.index) + } + onDownloadSet.remove(chapter.index) + chapterTasks.remove(chapter.index) } - onDownloadSet.remove(chapter.index) - chapterTasks.remove(chapter.index) } - @Synchronized private fun onPostError(chapter: BookChapter, error: Throwable) { - val retryCount = retryCountMap[chapter.index] ?: 0 - if (retryCount < 3 && !isStopped) { - queue.enqueue(ChapterSelection.Single(chapter.index)) - } else { - AppLog.put("下载${book.name}-${chapter.title}失败\n${error.localizedMessage}", error) + synchronized(this) { + val retryCount = retryCountMap[chapter.index] ?: 0 + if (retryCount < 3 && !isStopped) { + queue.enqueue(ChapterSelection.Single(chapter.index)) + } else { + AppLog.put("下载${book.name}-${chapter.title}失败\n${error.localizedMessage}", error) + } + waitingRetry = false } - waitingRetry = false } - @Synchronized private fun onError(chapter: BookChapter, error: Throwable) { onPreError(chapter, error) onPostError(chapter, error) @@ -302,21 +306,25 @@ class CacheBookModel( notifyErrorChanged() } - @Synchronized private fun onCancel(index: Int, requeue: Boolean = true) { - onDownloadSet.remove(index) - chapterTasks.remove(index) - val wasCanceled = canceledDownloadSet.remove(index) - if (requeue && !isStopped && !wasCanceled) { - queue.enqueue(ChapterSelection.Single(index)) + synchronized(this) { + onDownloadSet.remove(index) + chapterTasks.remove(index) + val wasCanceled = canceledDownloadSet.remove(index) + if (requeue && !isStopped && !wasCanceled) { + queue.enqueue(ChapterSelection.Single(index)) + } } notifyDownloadSetChanged() } - @Synchronized private fun onFinally() { + val shouldRemove: Boolean val bookUrl = book.bookUrl - if (queue.waitingCount() == 0 && onDownloadSet.isEmpty() && pausedChapterSet.isEmpty()) { + synchronized(this) { + shouldRemove = queue.waitingCount() == 0 && onDownloadSet.isEmpty() && pausedChapterSet.isEmpty() + } + if (shouldRemove) { host.onTaskRemoved(bookUrl) } else { host.onTaskQueuesChanged(bookUrl) diff --git a/app/src/main/java/io/legado/app/model/ImageProvider.kt b/app/src/main/java/io/legado/app/model/ImageProvider.kt index 9190334f4..ce38b932b 100644 --- a/app/src/main/java/io/legado/app/model/ImageProvider.kt +++ b/app/src/main/java/io/legado/app/model/ImageProvider.kt @@ -207,8 +207,10 @@ object ImageProvider { }.getOrDefault(errorBitmap) } - fun clear() { - bitmapLruCache.evictAll() + suspend fun clear() { + withContext(IO) { + bitmapLruCache.evictAll() + } } } diff --git a/app/src/main/java/io/legado/app/model/ReadBook.kt b/app/src/main/java/io/legado/app/model/ReadBook.kt index d7b42f49d..885bb69b0 100644 --- a/app/src/main/java/io/legado/app/model/ReadBook.kt +++ b/app/src/main/java/io/legado/app/model/ReadBook.kt @@ -1108,10 +1108,13 @@ object ReadBook : CoroutineScope by MainScope(), KoinComponent { preDownloadTask?.cancel() downloadScope.coroutineContext.cancelChildren() coroutineContext.cancelChildren() - ImageProvider.clear() clearExpiredChapterLoadingJob(true) - if (!CacheBookService.isRun) { - CacheBook.close() + // Move expensive cleanup off the main thread + CoroutineScope(SupervisorJob() + IO).launch { + ImageProvider.clear() + if (!CacheBookService.isRun) { + CacheBook.close() + } } } diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt index 63dc277c1..f28732827 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt @@ -212,9 +212,16 @@ class BookshelfViewModel( } combine(flows) { it.toMap() } } - }.flowOn(Dispatchers.Default) + }.distinctUntilChanged() + .flowOn(Dispatchers.Default) .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyMap()) + private val allGroupBooksImmutableFlow: StateFlow>> = + allGroupBooksFlow.map { map -> + map.mapValues { it.value.toImmutableList() }.toImmutableMap() + }.flowOn(Dispatchers.Default) + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), persistentMapOf()) + private val visibleBooksFlow = combine( booksFlow, searchKeyFlow, @@ -366,7 +373,7 @@ class BookshelfViewModel( ) { books, groups, allGroups, previews, internal -> BookshelfDataCore(books, groups, allGroups, previews, internal) }, - allGroupBooksFlow + allGroupBooksImmutableFlow ) { core, allGroupBooks -> BookshelfDataState( books = core.books, @@ -392,7 +399,7 @@ class BookshelfViewModel( val allGroups: List, val previews: GroupPreviewState, val internal: InternalState, - val allGroupBooks: Map> + val allGroupBooks: ImmutableMap> ) val uiState: StateFlow = combine( @@ -453,7 +460,7 @@ class BookshelfViewModel( currentGroupName = currentGroupName, draggingBooks = interaction.draggingBooks?.toImmutableList(), pendingSavedBooks = interaction.pendingSavedBooks?.toImmutableList(), - allGroupBooks = data.allGroupBooks.mapValues { it.value.toImmutableList() }.toImmutableMap() + allGroupBooks = data.allGroupBooks ) }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), BookshelfUiState())