Improve Loading Speed When Skipping Pages in a Chapter (#2426)
* cancel queued loads when the page that requested the queue is destroyed * use page.status for optimizing removal
This commit is contained in:
parent
78689e7443
commit
dd1e6402c9
1 changed files with 44 additions and 29 deletions
|
@ -17,6 +17,7 @@ import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.util.concurrent.PriorityBlockingQueue
|
import java.util.concurrent.PriorityBlockingQueue
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loader used to load chapters from an online source.
|
* Loader used to load chapters from an online source.
|
||||||
|
@ -37,18 +38,20 @@ class HttpPageLoader(
|
||||||
*/
|
*/
|
||||||
private val subscriptions = CompositeSubscription()
|
private val subscriptions = CompositeSubscription()
|
||||||
|
|
||||||
|
private val preloadSize = 4
|
||||||
|
|
||||||
init {
|
init {
|
||||||
subscriptions += Observable.defer { Observable.just(queue.take().page) }
|
subscriptions += Observable.defer { Observable.just(queue.take().page) }
|
||||||
.filter { it.status == Page.QUEUE }
|
.filter { it.status == Page.QUEUE }
|
||||||
.concatMap { source.fetchImageFromCacheThenNet(it) }
|
.concatMap { source.fetchImageFromCacheThenNet(it) }
|
||||||
.repeat()
|
.repeat()
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.subscribe({
|
.subscribe({
|
||||||
}, { error ->
|
}, { error ->
|
||||||
if (error !is InterruptedException) {
|
if (error !is InterruptedException) {
|
||||||
Timber.e(error)
|
Timber.e(error)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -80,13 +83,13 @@ class HttpPageLoader(
|
||||||
*/
|
*/
|
||||||
override fun getPages(): Observable<List<ReaderPage>> {
|
override fun getPages(): Observable<List<ReaderPage>> {
|
||||||
return chapterCache
|
return chapterCache
|
||||||
.getPageListFromCache(chapter.chapter)
|
.getPageListFromCache(chapter.chapter)
|
||||||
.onErrorResumeNext { source.fetchPageList(chapter.chapter) }
|
.onErrorResumeNext { source.fetchPageList(chapter.chapter) }
|
||||||
.map { pages ->
|
.map { pages ->
|
||||||
pages.mapIndexed { index, page -> // Don't trust sources and use our own indexing
|
pages.mapIndexed { index, page -> // Don't trust sources and use our own indexing
|
||||||
ReaderPage(index, page.url, page.imageUrl)
|
ReaderPage(index, page.url, page.imageUrl)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -110,29 +113,41 @@ class HttpPageLoader(
|
||||||
val statusSubject = SerializedSubject(PublishSubject.create<Int>())
|
val statusSubject = SerializedSubject(PublishSubject.create<Int>())
|
||||||
page.setStatusSubject(statusSubject)
|
page.setStatusSubject(statusSubject)
|
||||||
|
|
||||||
|
val queuedPages = mutableListOf<PriorityPage>()
|
||||||
if (page.status == Page.QUEUE) {
|
if (page.status == Page.QUEUE) {
|
||||||
queue.offer(PriorityPage(page, 1))
|
queuedPages += PriorityPage(page, 1).also { queue.offer(it) }
|
||||||
}
|
}
|
||||||
|
queuedPages += preloadNextPages(page, preloadSize)
|
||||||
preloadNextPages(page, 4)
|
|
||||||
|
|
||||||
statusSubject.startWith(page.status)
|
statusSubject.startWith(page.status)
|
||||||
|
.doOnUnsubscribe {
|
||||||
|
queuedPages.forEach {
|
||||||
|
if (it.page.status == Page.QUEUE) {
|
||||||
|
queue.remove(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.unsubscribeOn(Schedulers.io())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Preloads the given [amount] of pages after the [currentPage] with a lower priority.
|
* Preloads the given [amount] of pages after the [currentPage] with a lower priority.
|
||||||
|
* @return a list of [PriorityPage] that were added to the [queue]
|
||||||
*/
|
*/
|
||||||
private fun preloadNextPages(currentPage: ReaderPage, amount: Int) {
|
private fun preloadNextPages(currentPage: ReaderPage, amount: Int): List<PriorityPage> {
|
||||||
val pageIndex = currentPage.index
|
val pageIndex = currentPage.index
|
||||||
val pages = currentPage.chapter.pages ?: return
|
val pages = currentPage.chapter.pages ?: return emptyList()
|
||||||
if (pageIndex == pages.lastIndex) return
|
if (pageIndex == pages.lastIndex) return emptyList()
|
||||||
val nextPages = pages.subList(pageIndex + 1, Math.min(pageIndex + 1 + amount, pages.size))
|
|
||||||
for (nextPage in nextPages) {
|
return pages
|
||||||
if (nextPage.status == Page.QUEUE) {
|
.subList(pageIndex + 1, min(pageIndex + 1 + amount, pages.size))
|
||||||
queue.offer(PriorityPage(nextPage, 0))
|
.mapNotNull {
|
||||||
}
|
if (it.status == Page.QUEUE) {
|
||||||
}
|
PriorityPage(it, 0).apply { queue.offer(this) }
|
||||||
|
} else null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -148,7 +163,7 @@ class HttpPageLoader(
|
||||||
/**
|
/**
|
||||||
* Data class used to keep ordering of pages in order to maintain priority.
|
* Data class used to keep ordering of pages in order to maintain priority.
|
||||||
*/
|
*/
|
||||||
private data class PriorityPage(
|
private class PriorityPage(
|
||||||
val page: ReaderPage,
|
val page: ReaderPage,
|
||||||
val priority: Int
|
val priority: Int
|
||||||
): Comparable<PriorityPage> {
|
): Comparable<PriorityPage> {
|
||||||
|
|
Reference in a new issue