This commit is contained in:
Horis 2024-02-14 23:09:18 +08:00
parent c45f138fd5
commit 4119231dd0
10 changed files with 104 additions and 44 deletions

View File

@ -1,6 +1,5 @@
package io.legado.app.help.coroutine
import android.os.Looper
import io.legado.app.utils.printOnDebug
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CompletionHandler
@ -16,7 +15,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.plus
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
import java.util.concurrent.Executors
import kotlin.coroutines.CoroutineContext
/**
@ -35,9 +33,6 @@ class Coroutine<T>(
companion object {
private val DEFAULT = MainScope()
private val launchExecutor = Executors.newSingleThreadExecutor()
private val mainThread = Looper.getMainLooper().thread
private val isMainThread inline get() = mainThread === Thread.currentThread()
fun <T> async(
scope: CoroutineScope = DEFAULT,
@ -51,7 +46,7 @@ class Coroutine<T>(
}
private val job: Job by lazy { executeInternal(context, block) }
private val job: Job
private var start: VoidCallback? = null
private var success: Callback<T>? = null
@ -72,13 +67,7 @@ class Coroutine<T>(
get() = job.isCompleted
init {
if (context == Dispatchers.Main.immediate && isMainThread) {
job
} else {
launchExecutor.execute {
job
}
}
this.job = executeInternal(context, block)
}
fun timeout(timeMillis: () -> Long): Coroutine<T> {

View File

@ -116,6 +116,7 @@ import io.legado.app.utils.toastOnUi
import io.legado.app.utils.visible
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Job
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -220,6 +221,7 @@ class ReadBookActivity : BaseReadBookActivity(),
private var reloadContent = false
private val handler by lazy { buildMainHandler() }
private val screenOffRunnable by lazy { Runnable { keepScreenOn(false) } }
private val executor = IO.asExecutor()
//恢复跳转前进度对话框的交互结果
private var confirmRestoreProcess: Boolean? = null
@ -908,7 +910,7 @@ class ReadBookActivity : BaseReadBookActivity(),
}
override fun upMenuView() {
lifecycleScope.launch {
handler.post {
binding.readMenu.upBookView()
}
}
@ -930,7 +932,6 @@ class ReadBookActivity : BaseReadBookActivity(),
ReadAloud.upTtsProgress(this)
}
loadStates = true
binding.readView.onContentLoadFinish()
}
/**
@ -964,8 +965,13 @@ class ReadBookActivity : BaseReadBookActivity(),
*/
override fun pageChanged() {
pageChanged = true
runOnUiThread {
binding.readView.onPageChange()
}
handler.post {
upSeekBarProgress()
}
executor.execute {
startBackupJob()
}
}

View File

@ -8,7 +8,6 @@ import android.view.MotionEvent
import android.view.View
import androidx.core.graphics.withTranslation
import io.legado.app.R
import io.legado.app.constant.PageAnim
import io.legado.app.data.entities.Bookmark
import io.legado.app.help.config.AppConfig
import io.legado.app.model.ReadBook
@ -24,12 +23,12 @@ import io.legado.app.ui.book.read.page.entities.column.TextColumn
import io.legado.app.ui.book.read.page.provider.ChapterProvider
import io.legado.app.ui.book.read.page.provider.TextPageFactory
import io.legado.app.ui.widget.dialog.PhotoDialog
import io.legado.app.utils.PictureMirror
import io.legado.app.utils.activity
import io.legado.app.utils.getCompatColor
import io.legado.app.utils.showDialogFragment
import io.legado.app.utils.toastOnUi
import java.util.concurrent.Executors
import java.util.concurrent.ThreadFactory
import kotlin.math.min
/**
@ -58,10 +57,12 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
private val pageFactory get() = callBack.pageFactory
private val pageDelegate get() = callBack.pageDelegate
private var pageOffset = 0
private val pictureMirror = PictureMirror()
private val isNoAnim get() = ReadBook.pageAnim() == PageAnim.noAnim
private var autoPager: AutoPager? = null
private val renderThread by lazy { Executors.newSingleThreadExecutor() }
private val renderThread by lazy {
Executors.newSingleThreadExecutor {
Thread(it, "TextPageRender")
}
}
private val renderRunnable by lazy { Runnable { preRenderPage() } }
//绘制图片的paint
@ -99,13 +100,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
}
check(!visibleRect.isEmpty) { "visibleRect 为空" }
canvas.clipRect(visibleRect)
if (!callBack.isScroll && !isNoAnim) {
pictureMirror.draw(canvas, width, height) {
drawPage(this)
}
} else {
drawPage(canvas)
}
drawPage(canvas)
}
/**
@ -177,11 +172,6 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
invalidate()
}
override fun invalidate() {
super.invalidate()
pictureMirror.invalidate()
}
fun submitPreRenderTask() {
renderThread.submit(renderRunnable)
}

View File

@ -644,12 +644,7 @@ class ReadView(context: Context, attrs: AttributeSet) :
autoPager.resume()
}
override fun onPageChange() {
autoPager.reset()
curPage.submitPreRenderTask()
}
fun onContentLoadFinish() {
fun onPageChange() {
autoPager.reset()
curPage.submitPreRenderTask()
}

View File

@ -21,5 +21,4 @@ interface DataSource {
fun upContent(relativePosition: Int = 0, resetPageOffset: Boolean = true)
fun onPageChange()
}

View File

@ -4,6 +4,8 @@ package io.legado.app.ui.book.read.page.entities
import androidx.annotation.Keep
import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.ReplaceRule
import io.legado.app.utils.fastBinarySearchBy
import kotlin.math.abs
import kotlin.math.min
/**
@ -85,12 +87,15 @@ data class TextChapter(
* @return 已读长度
*/
fun getReadLength(pageIndex: Int): Int {
return pages[min(pageIndex, lastIndex)].lines.first().chapterPosition
/*
var length = 0
val maxIndex = min(pageIndex, pages.size)
for (index in 0 until maxIndex) {
length += pages[index].charSize
}
return length
*/
}
/**
@ -185,6 +190,15 @@ data class TextChapter(
* @return 根据索引位置获取所在页
*/
fun getPageIndexByCharIndex(charIndex: Int): Int {
val index = pages.fastBinarySearchBy(charIndex) {
it.lines.first().chapterPosition
}
return if (index >= 0) {
index
} else {
abs(index + 1) - 1
}
/* 相当于以下实现
var length = 0
for (i in pages.indices) {
val page = pages[i]
@ -194,6 +208,7 @@ data class TextChapter(
}
}
return pages.lastIndex
*/
}
fun clearSearchResult() {

View File

@ -264,7 +264,7 @@ data class TextPage(
}
fun draw(view: ContentTextView, canvas: Canvas?) {
pictureMirror.draw(canvas, view.width, height.toInt()) {
pictureMirror.drawLocked(canvas, view.width, height.toInt(), view) {
drawPage(view, this)
}
}

View File

@ -44,7 +44,6 @@ class TextPageFactory(dataSource: DataSource) : PageFactory<TextPage>(dataSource
ReadBook.setPageIndex(pageIndex.plus(1))
}
if (upContent) upContent(resetPageOffset = false)
dataSource.onPageChange()
true
} else
false
@ -64,7 +63,6 @@ class TextPageFactory(dataSource: DataSource) : PageFactory<TextPage>(dataSource
ReadBook.setPageIndex(pageIndex.minus(1))
}
if (upContent) upContent(resetPageOffset = false)
dataSource.onPageChange()
true
} else
false

View File

@ -7,3 +7,33 @@ fun List<Float>.fastSum(): Float {
}
return sum
}
inline fun <T> List<T>.fastBinarySearch(
fromIndex: Int = 0,
toIndex: Int = size,
comparison: (T) -> Int
): Int {
var low = fromIndex
var high = toIndex - 1
while (low <= high) {
val mid = (low + high).ushr(1) // safe from overflows
val midVal = get(mid)
val cmp = comparison(midVal)
if (cmp < 0)
low = mid + 1
else if (cmp > 0)
high = mid - 1
else
return mid // key found
}
return -(low + 1) // key not found
}
inline fun <T, K : Comparable<K>> List<T>.fastBinarySearchBy(
key: K?,
fromIndex: Int = 0,
toIndex: Int = size,
crossinline selector: (T) -> K?
): Int = fastBinarySearch(fromIndex, toIndex) { compareValues(selector(it), key) }

View File

@ -3,6 +3,7 @@ package io.legado.app.utils
import android.graphics.Canvas
import android.graphics.Picture
import android.os.Build
import android.view.View
import androidx.core.graphics.record
import java.util.concurrent.locks.ReentrantLock
@ -12,16 +13,31 @@ class PictureMirror {
@Volatile
var isDirty = true
val lock = ReentrantLock()
@Volatile
var scheduleInvalidateView: View? = null
inline fun draw(canvas: Canvas?, width: Int, height: Int, block: Canvas.() -> Unit) {
inline fun drawLocked(
canvas: Canvas?,
width: Int,
height: Int,
view: View? = null,
block: Canvas.() -> Unit
) {
if (atLeastApi23) {
if (picture == null) picture = Picture()
val picture = picture!!
if (isDirty) {
if (!lock.tryLock()) return
if (!lock.tryLock()) {
if (canvas != null && view != null) {
scheduleInvalidateView = view
}
return
}
try {
picture.record(width, height, block)
isDirty = false
scheduleInvalidateView?.postInvalidate()
scheduleInvalidateView = null
} finally {
lock.unlock()
}
@ -32,6 +48,28 @@ class PictureMirror {
}
}
/**
* 非线程安全多线程调用可能会崩溃
*/
inline fun draw(
canvas: Canvas?,
width: Int,
height: Int,
block: Canvas.() -> Unit
) {
if (atLeastApi23) {
if (picture == null) picture = Picture()
val picture = picture!!
if (isDirty) {
picture.record(width, height, block)
isDirty = false
}
canvas?.drawPicture(picture)
} else {
canvas?.block()
}
}
fun invalidate() {
isDirty = true
}