HapeLee 6 месяцев назад
Родитель
Сommit
890ab3a502

+ 37 - 29
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)

+ 4 - 2
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()
+        }
     }
 
 }

+ 6 - 3
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()
+            }
         }
     }
 

+ 11 - 4
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<ImmutableMap<Long, ImmutableList<BookShelfItem>>> =
+        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<BookGroupUi>,
         val previews: GroupPreviewState,
         val internal: InternalState,
-        val allGroupBooks: Map<Long, List<BookShelfItem>>
+        val allGroupBooks: ImmutableMap<Long, ImmutableList<BookShelfItem>>
     )
 
     val uiState: StateFlow<BookshelfUiState> = 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())