下载优化
This commit is contained in:
@@ -17,6 +17,7 @@ import io.legado.app.ui.config.otherConfig.OtherConfig
|
||||
import io.legado.app.utils.LogUtils
|
||||
import io.legado.app.utils.onEachParallel
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.TimeoutCancellationException
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.currentCoroutineContext
|
||||
@@ -34,6 +35,7 @@ import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
@@ -180,16 +182,14 @@ object CacheBook {
|
||||
_downloadSummaryFlow.value = buildSummary(stats)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun getOrCreate(bookUrl: String): CacheBookModel? {
|
||||
val book = appDb.bookDao.getBook(bookUrl) ?: return null
|
||||
val source = appDb.bookSourceDao.getBookSource(book.origin) ?: return null
|
||||
return getOrCreate(source, book)
|
||||
suspend fun getOrCreate(bookUrl: String): CacheBookModel? = withContext(Dispatchers.IO) {
|
||||
val book = appDb.bookDao.getBook(bookUrl) ?: return@withContext null
|
||||
val source = appDb.bookSourceDao.getBookSource(book.origin) ?: return@withContext null
|
||||
getOrCreate(source, book)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun getOrCreate(bookSource: BookSource, book: Book): CacheBookModel {
|
||||
updateBookSource(bookSource)
|
||||
cacheBookMap[book.bookUrl]?.let { model ->
|
||||
model.bookSource = bookSource
|
||||
model.book = book
|
||||
@@ -202,14 +202,16 @@ object CacheBook {
|
||||
}
|
||||
|
||||
private fun updateBookSource(newBookSource: BookSource) {
|
||||
cacheBookMap.forEach { (_, model) ->
|
||||
if (model.bookSource.bookSourceUrl == newBookSource.bookSourceUrl) {
|
||||
// 只有在必要时才更新,且避免在 getOrCreate 中高频调用
|
||||
val sourceUrl = newBookSource.bookSourceUrl
|
||||
cacheBookMap.values.forEach { model ->
|
||||
if (model.bookSource.bookSourceUrl == sourceUrl && model.bookSource != newBookSource) {
|
||||
model.bookSource = newBookSource
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun start(context: Context, book: Book, selectedIndices: List<Int>) {
|
||||
suspend fun start(context: Context, book: Book, selectedIndices: List<Int>) {
|
||||
start(
|
||||
context = context,
|
||||
request = CacheDownloadRequest(
|
||||
@@ -220,7 +222,7 @@ object CacheBook {
|
||||
)
|
||||
}
|
||||
|
||||
fun start(context: Context, book: Book, startIndex: Int, endIndex: Int) {
|
||||
suspend fun start(context: Context, book: Book, startIndex: Int, endIndex: Int) {
|
||||
start(
|
||||
context = context,
|
||||
request = CacheDownloadRequest(
|
||||
@@ -241,19 +243,28 @@ object CacheBook {
|
||||
}
|
||||
}
|
||||
|
||||
fun start(context: Context, requests: List<CacheDownloadRequest>) {
|
||||
requests.asSequence()
|
||||
.filter { it.hasValidSelection() }
|
||||
.filter { request ->
|
||||
appDb.bookDao.getBook(request.bookUrl)?.isLocal != true
|
||||
}
|
||||
.forEach { request ->
|
||||
isPaused = false
|
||||
startCacheBookService(context) {
|
||||
action = IntentAction.start
|
||||
putRequestExtras(request)
|
||||
}
|
||||
suspend fun start(context: Context, requests: List<CacheDownloadRequest>) = withContext(Dispatchers.IO) {
|
||||
val validRequests = requests.filter { it.hasValidSelection() }
|
||||
if (validRequests.isEmpty()) return@withContext
|
||||
|
||||
val urls = validRequests.map { it.bookUrl }.toSet()
|
||||
val localBookUrls = appDb.bookDao.getCacheableBooks(urls)
|
||||
.filter { it.isLocal }
|
||||
.map { it.bookUrl }
|
||||
.toSet()
|
||||
|
||||
val finalRequests = validRequests.filterNot { it.bookUrl in localBookUrls }
|
||||
if (finalRequests.isEmpty()) return@withContext
|
||||
|
||||
isPaused = false
|
||||
// 如果请求较多,可以通过 Intent 传递一个特殊的标志让 Service 自己去检查队列,
|
||||
// 或者分批发送。这里我们先简单处理,但确保不在主线程做数据库查询。
|
||||
finalRequests.forEach { request ->
|
||||
startCacheBookService(context) {
|
||||
action = IntentAction.start
|
||||
putRequestExtras(request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun android.content.Intent.putRequestExtras(request: CacheDownloadRequest) {
|
||||
|
||||
@@ -116,12 +116,21 @@ class CacheDownloadStateStore {
|
||||
}
|
||||
|
||||
private fun CacheDownloadState.recalculate(): CacheDownloadState {
|
||||
val totalWaiting = books.values.sumOf { it.waitingCount }
|
||||
val totalRunning = books.values.sumOf { it.runningIndices.size }
|
||||
val totalPaused = books.values.sumOf { it.pausedIndices.size }
|
||||
val totalFailure = books.values.sumOf { it.failedIndices.size } +
|
||||
books.values.count { it.failureMessage != null }
|
||||
val totalSuccess = books.values.sumOf { it.successCount }
|
||||
var totalWaiting = 0
|
||||
var totalRunning = 0
|
||||
var totalPaused = 0
|
||||
var totalFailure = 0
|
||||
var totalSuccess = 0
|
||||
|
||||
books.values.forEach { bookState ->
|
||||
totalWaiting += bookState.waitingCount
|
||||
totalRunning += bookState.runningIndices.size
|
||||
totalPaused += bookState.pausedIndices.size
|
||||
totalFailure += bookState.failedIndices.size
|
||||
if (bookState.failureMessage != null) totalFailure++
|
||||
totalSuccess += bookState.successCount
|
||||
}
|
||||
|
||||
return copy(
|
||||
isRunning = totalWaiting > totalPaused || totalRunning > 0,
|
||||
totalWaiting = totalWaiting,
|
||||
|
||||
@@ -150,7 +150,7 @@ class CacheBookService : BaseService() {
|
||||
serviceCommandMutex.withLock {
|
||||
CacheBook.pauseAllFromService()
|
||||
notificationContent = CacheBook.downloadSummary
|
||||
upCacheBookNotification()
|
||||
upCacheBookNotification(force = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -160,7 +160,7 @@ class CacheBookService : BaseService() {
|
||||
CacheBook.resumeFromService()
|
||||
ensureDownloadJob()
|
||||
notificationContent = CacheBook.downloadSummary
|
||||
upCacheBookNotification()
|
||||
upCacheBookNotification(force = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -364,7 +364,8 @@ class CacheBookService : BaseService() {
|
||||
}
|
||||
|
||||
notificationContent = CacheBook.downloadSummary
|
||||
upCacheBookNotification()
|
||||
// 移除这里的直接调用,依靠 onCreate 的循环更新
|
||||
// upCacheBookNotification()
|
||||
}
|
||||
|
||||
private fun AdmissionRequest.isCurrent(): Boolean {
|
||||
@@ -521,7 +522,13 @@ class CacheBookService : BaseService() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun upCacheBookNotification() {
|
||||
private var lastNotificationTime = 0L
|
||||
|
||||
private fun upCacheBookNotification(force: Boolean = false) {
|
||||
val now = System.currentTimeMillis()
|
||||
if (!force && now - lastNotificationTime < 500L) return
|
||||
lastNotificationTime = now
|
||||
|
||||
val total = CacheBook.totalCount
|
||||
val progress = CacheBook.completedCount
|
||||
val pendingBookCount = synchronized(admissionQueue) { admissionQueue.size }
|
||||
|
||||
@@ -13,6 +13,7 @@ import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.doOnAttach
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.datepicker.MaterialDatePicker
|
||||
import io.legado.app.R
|
||||
import io.legado.app.base.VMBaseActivity
|
||||
@@ -48,6 +49,7 @@ import io.legado.app.utils.isTv
|
||||
import io.legado.app.utils.setLightStatusBar
|
||||
import io.legado.app.utils.setNavigationBarColorAuto
|
||||
import io.legado.app.utils.setOnApplyWindowInsetsListenerCompat
|
||||
import kotlinx.coroutines.launch
|
||||
import io.legado.app.utils.showDialogFragment
|
||||
import io.legado.app.utils.themeColor
|
||||
import io.legado.app.utils.viewbindingdelegate.viewBinding
|
||||
@@ -304,7 +306,9 @@ abstract class BaseReadBookActivity :
|
||||
val end = editEnd.text!!.toString().let {
|
||||
if (it.isEmpty()) book.totalChapterNum else it.toInt()
|
||||
}
|
||||
CacheBook.start(this@BaseReadBookActivity, book, start - 1, end - 1)
|
||||
lifecycleScope.launch {
|
||||
CacheBook.start(this@BaseReadBookActivity, book, start - 1, end - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
cancelButton()
|
||||
|
||||
@@ -38,6 +38,7 @@ import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.AppFloatingActionButton
|
||||
import io.legado.app.ui.widget.components.AppTextField
|
||||
import io.legado.app.ui.widget.components.button.MediumIconButton
|
||||
import io.legado.app.ui.widget.components.icon.AppIcon
|
||||
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
|
||||
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
|
||||
|
||||
Reference in New Issue
Block a user