Jelajahi Sumber

[优化] 优化性能

HapeLee 6 bulan lalu
induk
melakukan
a3674ac5d9

+ 13 - 4
app/src/main/java/io/legado/app/data/repository/ExploreRepository.kt

@@ -4,7 +4,6 @@ import io.legado.app.data.AppDatabase
 import io.legado.app.data.entities.BookSource
 import io.legado.app.data.entities.BookSource
 import io.legado.app.data.entities.SearchBook
 import io.legado.app.data.entities.SearchBook
 import io.legado.app.data.entities.rule.ExploreKind
 import io.legado.app.data.entities.rule.ExploreKind
-import io.legado.app.help.book.isNotShelf
 import io.legado.app.help.source.exploreKinds
 import io.legado.app.help.source.exploreKinds
 import io.legado.app.model.webBook.WebBook
 import io.legado.app.model.webBook.WebBook
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Dispatchers
@@ -26,9 +25,19 @@ class ExploreRepositoryImpl(
 ) : ExploreRepository {
 ) : ExploreRepository {
 
 
     override fun getBookshelfItems(): Flow<List<SearchBook>> {
     override fun getBookshelfItems(): Flow<List<SearchBook>> {
-        return appDb.bookDao.flowAll().map { books ->
+        return appDb.bookDao.flowBookShelf().map { books ->
             books.filterNot { it.isNotShelf }
             books.filterNot { it.isNotShelf }
-                .map { it.toSearchBook() }
+                .map {
+                    SearchBook(
+                        bookUrl = it.bookUrl,
+                        name = it.name,
+                        author = it.author,
+                        originName = it.originName,
+                        type = it.type,
+                        coverUrl = it.coverUrl,
+                        latestChapterTitle = it.latestChapterTitle
+                    )
+                }
         }
         }
     }
     }
 
 
@@ -56,4 +65,4 @@ class ExploreRepositoryImpl(
     override suspend fun saveSearchBooks(books: List<SearchBook>) {
     override suspend fun saveSearchBooks(books: List<SearchBook>) {
         appDb.searchBookDao.insert(*books.toTypedArray())
         appDb.searchBookDao.insert(*books.toTypedArray())
     }
     }
-}
+}

+ 9 - 0
app/src/main/java/io/legado/app/help/AppFreezeMonitor.kt

@@ -24,6 +24,7 @@ object AppFreezeMonitor {
     }
     }
 
 
     private var registeredReceiver = false
     private var registeredReceiver = false
+    private var monitorRunnable: Runnable? = null
 
 
     @SuppressLint("UnspecifiedRegisterReceiverFlag")
     @SuppressLint("UnspecifiedRegisterReceiverFlag")
     fun init(context: Context) {
     fun init(context: Context) {
@@ -32,6 +33,10 @@ object AppFreezeMonitor {
                 registeredReceiver = false
                 registeredReceiver = false
                 context.unregisterReceiver(screenStatusReceiver)
                 context.unregisterReceiver(screenStatusReceiver)
             }
             }
+            monitorRunnable?.let {
+                handler.removeCallbacks(it)
+                monitorRunnable = null
+            }
             return
             return
         }
         }
 
 
@@ -40,6 +45,7 @@ object AppFreezeMonitor {
             context.registerReceiver(screenStatusReceiver, screenStatusReceiver.filter)
             context.registerReceiver(screenStatusReceiver, screenStatusReceiver.filter)
         }
         }
 
 
+        if (monitorRunnable != null) return
         var previous = SystemClock.uptimeMillis()
         var previous = SystemClock.uptimeMillis()
 
 
         val runnable = object : Runnable {
         val runnable = object : Runnable {
@@ -56,9 +62,12 @@ object AppFreezeMonitor {
 
 
                 if (AppConfig.recordLog) {
                 if (AppConfig.recordLog) {
                     handler.postDelayed(this, 3000)
                     handler.postDelayed(this, 3000)
+                } else {
+                    monitorRunnable = null
                 }
                 }
             }
             }
         }
         }
+        monitorRunnable = runnable
         handler.postDelayed(runnable, 3000)
         handler.postDelayed(runnable, 3000)
     }
     }
 
 

+ 75 - 16
app/src/main/java/io/legado/app/help/book/BookHelp.kt

@@ -428,28 +428,87 @@ object BookHelp {
         var ret = true
         var ret = true
         val op = BitmapFactory.Options()
         val op = BitmapFactory.Options()
         op.inJustDecodeBounds = true
         op.inJustDecodeBounds = true
-        getContent(book, bookChapter)?.let {
-            val matcher = AppPattern.imgPattern.matcher(it)
-            while (matcher.find()) {
-                val src = matcher.group(1)!!
-                val image = getImage(book, src)
-                if (!image.exists()) {
-                    ret = false
-                    continue
-                }
-                BitmapFactory.decodeFile(image.absolutePath, op)
-                if (op.outWidth < 1 && op.outHeight < 1) {
-                    if (SvgUtils.getSize(image.absolutePath) != null) {
-                        continue
-                    }
-                    ret = false
-                    image.delete()
+        forEachImageSrc(book, bookChapter) { src ->
+            val image = getImage(book, src)
+            if (!image.exists()) {
+                ret = false
+                return@forEachImageSrc
+            }
+            BitmapFactory.decodeFile(image.absolutePath, op)
+            if (op.outWidth < 1 && op.outHeight < 1) {
+                if (SvgUtils.getSize(image.absolutePath) != null) {
+                    return@forEachImageSrc
                 }
                 }
+                ret = false
+                image.delete()
             }
             }
         }
         }
         return ret
         return ret
     }
     }
 
 
+    private inline fun forEachImageSrc(
+        book: Book,
+        bookChapter: BookChapter,
+        action: (String) -> Unit
+    ) {
+        val file = downloadDir.getFile(
+            cacheFolderName,
+            book.getFolderName(),
+            bookChapter.getFileName()
+        )
+        if (file.exists()) {
+            forEachImageSrc(file, action)
+            return
+        }
+        getContent(book, bookChapter)?.let { content ->
+            val matcher = AppPattern.imgPattern.matcher(content)
+            while (matcher.find()) {
+                action(matcher.group(1) ?: continue)
+            }
+        }
+    }
+
+    private inline fun forEachImageSrc(file: File, action: (String) -> Unit) {
+        val chars = CharArray(8192)
+        val buffer = StringBuilder()
+        file.bufferedReader().use { reader ->
+            while (true) {
+                val readSize = reader.read(chars)
+                if (readSize < 0) break
+                buffer.append(chars, 0, readSize)
+                consumeImageSrcMatches(buffer, action)
+                trimImageScanBuffer(buffer)
+            }
+            consumeImageSrcMatches(buffer, action)
+        }
+    }
+
+    private inline fun consumeImageSrcMatches(
+        buffer: StringBuilder,
+        action: (String) -> Unit
+    ) {
+        val matcher = AppPattern.imgPattern.matcher(buffer)
+        var lastEnd = 0
+        while (matcher.find()) {
+            action(matcher.group(1) ?: continue)
+            lastEnd = matcher.end()
+        }
+        if (lastEnd > 0) {
+            buffer.delete(0, lastEnd)
+        }
+    }
+
+    private fun trimImageScanBuffer(buffer: StringBuilder) {
+        val maxBufferSize = 16 * 1024
+        if (buffer.length <= maxBufferSize) return
+        val partialImgStart = buffer.lastIndexOf("<img")
+        val keepFrom = when {
+            partialImgStart >= 0 -> partialImgStart
+            else -> max(buffer.length - 1024, 0)
+        }
+        buffer.delete(0, keepFrom)
+    }
+
     private fun checkImage(bytes: ByteArray): Boolean {
     private fun checkImage(bytes: ByteArray): Boolean {
         val op = BitmapFactory.Options()
         val op = BitmapFactory.Options()
         op.inJustDecodeBounds = true
         op.inJustDecodeBounds = true

+ 7 - 7
app/src/main/java/io/legado/app/model/CacheBook.kt

@@ -103,6 +103,8 @@ object CacheBook {
     private val _downloadErrorFlow =
     private val _downloadErrorFlow =
         MutableStateFlow<Pair<String, Set<Int>>>("" to emptySet())
         MutableStateFlow<Pair<String, Set<Int>>>("" to emptySet())
     val downloadErrorFlow = _downloadErrorFlow.asStateFlow()
     val downloadErrorFlow = _downloadErrorFlow.asStateFlow()
+    @Volatile
+    private var lastQueueStats = QueueStats(0, 0)
 
 
     val successDownloadSet = ConcurrentHashMap.newKeySet<String>()
     val successDownloadSet = ConcurrentHashMap.newKeySet<String>()
     val errorDownloadMap = ConcurrentHashMap<String, Int>()
     val errorDownloadMap = ConcurrentHashMap<String, Int>()
@@ -123,7 +125,10 @@ object CacheBook {
     }
     }
 
 
     private fun updateSummary() {
     private fun updateSummary() {
-        _downloadSummaryFlow.value = downloadSummary
+        val stats = collectQueueStats()
+        lastQueueStats = stats
+        _downloadSummaryFlow.value =
+            "正在下载:${stats.downloadingCount}|等待中:${stats.waitingCount}|失败:${errorDownloadMap.size}|成功:${successDownloadSet.size}"
     }
     }
 
 
     @Synchronized
     @Synchronized
@@ -213,12 +218,7 @@ object CacheBook {
         }
         }
 
 
     val isRun: Boolean
     val isRun: Boolean
-        get() {
-            cacheBookMap.forEach { (_, model) ->
-                if (model.isRun()) return true
-            }
-            return false
-        }
+        get() = lastQueueStats.waitingCount > 0 || lastQueueStats.downloadingCount > 0
 
 
     private fun onTaskQueuesChanged(bookUrl: String) {
     private fun onTaskQueuesChanged(bookUrl: String) {
         updateSummary()
         updateSummary()

+ 3 - 2
app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt

@@ -21,6 +21,7 @@ import io.legado.app.model.CacheBook
 import io.legado.app.help.config.LocalConfig
 import io.legado.app.help.config.LocalConfig
 import io.legado.app.service.ExportBookService
 import io.legado.app.service.ExportBookService
 import io.legado.app.ui.config.cacheConfig.CacheConfig
 import io.legado.app.ui.config.cacheConfig.CacheConfig
+import io.legado.app.ui.main.bookshelf.toLightBook
 import io.legado.app.utils.cnCompare
 import io.legado.app.utils.cnCompare
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.ensureActive
 import kotlinx.coroutines.ensureActive
@@ -219,8 +220,8 @@ class CacheViewModel(
     private fun observeBooks(groupId: Long) {
     private fun observeBooks(groupId: Long) {
         booksJob?.cancel()
         booksJob?.cancel()
         booksJob = viewModelScope.launch {
         booksJob = viewModelScope.launch {
-            bookDao.flowByGroup(groupId).map { books ->
-                val booksDownload = books.filter { !it.isAudio }
+            bookDao.flowBookShelfByGroup(groupId).map { books ->
+                val booksDownload = books.filter { !it.isAudio }.map { it.toLightBook() }
                 when (cacheConfig.getBookSortByGroupId(groupId)) {
                 when (cacheConfig.getBookSortByGroupId(groupId)) {
                     1 -> booksDownload.sortedByDescending { it.latestChapterTime }
                     1 -> booksDownload.sortedByDescending { it.latestChapterTime }
                     2 -> booksDownload.sortedWith { o1, o2 -> o1.name.cnCompare(o2.name) }
                     2 -> booksDownload.sortedWith { o1, o2 -> o1.name.cnCompare(o2.name) }

+ 22 - 0
app/src/main/java/io/legado/app/ui/main/bookshelf/BookShelfItem.kt

@@ -2,6 +2,7 @@ package io.legado.app.ui.main.bookshelf
 
 
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.Stable
 import io.legado.app.constant.BookType
 import io.legado.app.constant.BookType
+import io.legado.app.data.entities.Book
 import kotlin.math.max
 import kotlin.math.max
 
 
 @Stable
 @Stable
@@ -40,3 +41,24 @@ data class BookShelfItem(
 
 
     fun getUnreadChapterNum() = max(totalChapterNum - durChapterIndex - 1, 0)
     fun getUnreadChapterNum() = max(totalChapterNum - durChapterIndex - 1, 0)
 }
 }
+
+fun BookShelfItem.toLightBook() = Book(
+    bookUrl = bookUrl,
+    originName = originName,
+    name = name,
+    author = author,
+    coverUrl = coverUrl,
+    customCoverUrl = customCoverUrl,
+    latestChapterTitle = latestChapterTitle,
+    latestChapterTime = latestChapterTime,
+    lastCheckCount = lastCheckCount,
+    totalChapterNum = totalChapterNum,
+    durChapterTitle = durChapterTitle,
+    durChapterIndex = durChapterIndex,
+    durChapterPos = durChapterPos,
+    durChapterTime = durChapterTime,
+    type = type,
+    group = group,
+    order = order,
+    canUpdate = canUpdate
+)