[优化] 优化性能
This commit is contained in:
@@ -4,7 +4,6 @@ import io.legado.app.data.AppDatabase
|
||||
import io.legado.app.data.entities.BookSource
|
||||
import io.legado.app.data.entities.SearchBook
|
||||
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.model.webBook.WebBook
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -26,9 +25,19 @@ class ExploreRepositoryImpl(
|
||||
) : ExploreRepository {
|
||||
|
||||
override fun getBookshelfItems(): Flow<List<SearchBook>> {
|
||||
return appDb.bookDao.flowAll().map { books ->
|
||||
return appDb.bookDao.flowBookShelf().map { books ->
|
||||
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>) {
|
||||
appDb.searchBookDao.insert(*books.toTypedArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ object AppFreezeMonitor {
|
||||
}
|
||||
|
||||
private var registeredReceiver = false
|
||||
private var monitorRunnable: Runnable? = null
|
||||
|
||||
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
||||
fun init(context: Context) {
|
||||
@@ -32,6 +33,10 @@ object AppFreezeMonitor {
|
||||
registeredReceiver = false
|
||||
context.unregisterReceiver(screenStatusReceiver)
|
||||
}
|
||||
monitorRunnable?.let {
|
||||
handler.removeCallbacks(it)
|
||||
monitorRunnable = null
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -40,6 +45,7 @@ object AppFreezeMonitor {
|
||||
context.registerReceiver(screenStatusReceiver, screenStatusReceiver.filter)
|
||||
}
|
||||
|
||||
if (monitorRunnable != null) return
|
||||
var previous = SystemClock.uptimeMillis()
|
||||
|
||||
val runnable = object : Runnable {
|
||||
@@ -56,9 +62,12 @@ object AppFreezeMonitor {
|
||||
|
||||
if (AppConfig.recordLog) {
|
||||
handler.postDelayed(this, 3000)
|
||||
} else {
|
||||
monitorRunnable = null
|
||||
}
|
||||
}
|
||||
}
|
||||
monitorRunnable = runnable
|
||||
handler.postDelayed(runnable, 3000)
|
||||
}
|
||||
|
||||
|
||||
@@ -428,28 +428,87 @@ object BookHelp {
|
||||
var ret = true
|
||||
val op = BitmapFactory.Options()
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
val op = BitmapFactory.Options()
|
||||
op.inJustDecodeBounds = true
|
||||
|
||||
@@ -103,6 +103,8 @@ object CacheBook {
|
||||
private val _downloadErrorFlow =
|
||||
MutableStateFlow<Pair<String, Set<Int>>>("" to emptySet())
|
||||
val downloadErrorFlow = _downloadErrorFlow.asStateFlow()
|
||||
@Volatile
|
||||
private var lastQueueStats = QueueStats(0, 0)
|
||||
|
||||
val successDownloadSet = ConcurrentHashMap.newKeySet<String>()
|
||||
val errorDownloadMap = ConcurrentHashMap<String, Int>()
|
||||
@@ -123,7 +125,10 @@ object CacheBook {
|
||||
}
|
||||
|
||||
private fun updateSummary() {
|
||||
_downloadSummaryFlow.value = downloadSummary
|
||||
val stats = collectQueueStats()
|
||||
lastQueueStats = stats
|
||||
_downloadSummaryFlow.value =
|
||||
"正在下载:${stats.downloadingCount}|等待中:${stats.waitingCount}|失败:${errorDownloadMap.size}|成功:${successDownloadSet.size}"
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
@@ -213,12 +218,7 @@ object CacheBook {
|
||||
}
|
||||
|
||||
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) {
|
||||
updateSummary()
|
||||
|
||||
@@ -21,6 +21,7 @@ import io.legado.app.model.CacheBook
|
||||
import io.legado.app.help.config.LocalConfig
|
||||
import io.legado.app.service.ExportBookService
|
||||
import io.legado.app.ui.config.cacheConfig.CacheConfig
|
||||
import io.legado.app.ui.main.bookshelf.toLightBook
|
||||
import io.legado.app.utils.cnCompare
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.ensureActive
|
||||
@@ -219,8 +220,8 @@ class CacheViewModel(
|
||||
private fun observeBooks(groupId: Long) {
|
||||
booksJob?.cancel()
|
||||
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)) {
|
||||
1 -> booksDownload.sortedByDescending { it.latestChapterTime }
|
||||
2 -> booksDownload.sortedWith { o1, o2 -> o1.name.cnCompare(o2.name) }
|
||||
|
||||
@@ -2,6 +2,7 @@ package io.legado.app.ui.main.bookshelf
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import io.legado.app.constant.BookType
|
||||
import io.legado.app.data.entities.Book
|
||||
import kotlin.math.max
|
||||
|
||||
@Stable
|
||||
@@ -40,3 +41,24 @@ data class BookShelfItem(
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user