This commit is contained in:
Horis
2024-02-14 23:09:18 +08:00
parent 9f8450c4f1
commit e5148d1a05
10 changed files with 104 additions and 44 deletions
@@ -1,6 +1,5 @@
package io.legado.app.help.coroutine package io.legado.app.help.coroutine
import android.os.Looper
import io.legado.app.utils.printOnDebug import io.legado.app.utils.printOnDebug
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CompletionHandler import kotlinx.coroutines.CompletionHandler
@@ -16,7 +15,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.plus import kotlinx.coroutines.plus
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout import kotlinx.coroutines.withTimeout
import java.util.concurrent.Executors
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
/** /**
@@ -35,9 +33,6 @@ class Coroutine<T>(
companion object { companion object {
private val DEFAULT = MainScope() 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( fun <T> async(
scope: CoroutineScope = DEFAULT, 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 start: VoidCallback? = null
private var success: Callback<T>? = null private var success: Callback<T>? = null
@@ -72,13 +67,7 @@ class Coroutine<T>(
get() = job.isCompleted get() = job.isCompleted
init { init {
if (context == Dispatchers.Main.immediate && isMainThread) { this.job = executeInternal(context, block)
job
} else {
launchExecutor.execute {
job
}
}
} }
fun timeout(timeMillis: () -> Long): Coroutine<T> { fun timeout(timeMillis: () -> Long): Coroutine<T> {
@@ -116,6 +116,7 @@ import io.legado.app.utils.toastOnUi
import io.legado.app.utils.visible import io.legado.app.utils.visible
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -220,6 +221,7 @@ class ReadBookActivity : BaseReadBookActivity(),
private var reloadContent = false private var reloadContent = false
private val handler by lazy { buildMainHandler() } private val handler by lazy { buildMainHandler() }
private val screenOffRunnable by lazy { Runnable { keepScreenOn(false) } } private val screenOffRunnable by lazy { Runnable { keepScreenOn(false) } }
private val executor = IO.asExecutor()
//恢复跳转前进度对话框的交互结果 //恢复跳转前进度对话框的交互结果
private var confirmRestoreProcess: Boolean? = null private var confirmRestoreProcess: Boolean? = null
@@ -908,7 +910,7 @@ class ReadBookActivity : BaseReadBookActivity(),
} }
override fun upMenuView() { override fun upMenuView() {
lifecycleScope.launch { handler.post {
binding.readMenu.upBookView() binding.readMenu.upBookView()
} }
} }
@@ -930,7 +932,6 @@ class ReadBookActivity : BaseReadBookActivity(),
ReadAloud.upTtsProgress(this) ReadAloud.upTtsProgress(this)
} }
loadStates = true loadStates = true
binding.readView.onContentLoadFinish()
} }
/** /**
@@ -964,8 +965,13 @@ class ReadBookActivity : BaseReadBookActivity(),
*/ */
override fun pageChanged() { override fun pageChanged() {
pageChanged = true pageChanged = true
runOnUiThread {
binding.readView.onPageChange()
}
handler.post { handler.post {
upSeekBarProgress() upSeekBarProgress()
}
executor.execute {
startBackupJob() startBackupJob()
} }
} }
@@ -8,7 +8,6 @@ import android.view.MotionEvent
import android.view.View import android.view.View
import androidx.core.graphics.withTranslation import androidx.core.graphics.withTranslation
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.PageAnim
import io.legado.app.data.entities.Bookmark import io.legado.app.data.entities.Bookmark
import io.legado.app.help.config.AppConfig import io.legado.app.help.config.AppConfig
import io.legado.app.model.ReadBook 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.ChapterProvider
import io.legado.app.ui.book.read.page.provider.TextPageFactory import io.legado.app.ui.book.read.page.provider.TextPageFactory
import io.legado.app.ui.widget.dialog.PhotoDialog 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.activity
import io.legado.app.utils.getCompatColor import io.legado.app.utils.getCompatColor
import io.legado.app.utils.showDialogFragment import io.legado.app.utils.showDialogFragment
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
import java.util.concurrent.Executors import java.util.concurrent.Executors
import java.util.concurrent.ThreadFactory
import kotlin.math.min 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 pageFactory get() = callBack.pageFactory
private val pageDelegate get() = callBack.pageDelegate private val pageDelegate get() = callBack.pageDelegate
private var pageOffset = 0 private var pageOffset = 0
private val pictureMirror = PictureMirror()
private val isNoAnim get() = ReadBook.pageAnim() == PageAnim.noAnim
private var autoPager: AutoPager? = null 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() } } private val renderRunnable by lazy { Runnable { preRenderPage() } }
//绘制图片的paint //绘制图片的paint
@@ -99,13 +100,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
} }
check(!visibleRect.isEmpty) { "visibleRect 为空" } check(!visibleRect.isEmpty) { "visibleRect 为空" }
canvas.clipRect(visibleRect) canvas.clipRect(visibleRect)
if (!callBack.isScroll && !isNoAnim) { drawPage(canvas)
pictureMirror.draw(canvas, width, height) {
drawPage(this)
}
} else {
drawPage(canvas)
}
} }
/** /**
@@ -177,11 +172,6 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
invalidate() invalidate()
} }
override fun invalidate() {
super.invalidate()
pictureMirror.invalidate()
}
fun submitPreRenderTask() { fun submitPreRenderTask() {
renderThread.submit(renderRunnable) renderThread.submit(renderRunnable)
} }
@@ -644,12 +644,7 @@ class ReadView(context: Context, attrs: AttributeSet) :
autoPager.resume() autoPager.resume()
} }
override fun onPageChange() { fun onPageChange() {
autoPager.reset()
curPage.submitPreRenderTask()
}
fun onContentLoadFinish() {
autoPager.reset() autoPager.reset()
curPage.submitPreRenderTask() curPage.submitPreRenderTask()
} }
@@ -21,5 +21,4 @@ interface DataSource {
fun upContent(relativePosition: Int = 0, resetPageOffset: Boolean = true) fun upContent(relativePosition: Int = 0, resetPageOffset: Boolean = true)
fun onPageChange()
} }
@@ -4,6 +4,8 @@ package io.legado.app.ui.book.read.page.entities
import androidx.annotation.Keep import androidx.annotation.Keep
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.ReplaceRule import io.legado.app.data.entities.ReplaceRule
import io.legado.app.utils.fastBinarySearchBy
import kotlin.math.abs
import kotlin.math.min import kotlin.math.min
/** /**
@@ -85,12 +87,15 @@ data class TextChapter(
* @return 已读长度 * @return 已读长度
*/ */
fun getReadLength(pageIndex: Int): Int { fun getReadLength(pageIndex: Int): Int {
return pages[min(pageIndex, lastIndex)].lines.first().chapterPosition
/*
var length = 0 var length = 0
val maxIndex = min(pageIndex, pages.size) val maxIndex = min(pageIndex, pages.size)
for (index in 0 until maxIndex) { for (index in 0 until maxIndex) {
length += pages[index].charSize length += pages[index].charSize
} }
return length return length
*/
} }
/** /**
@@ -185,6 +190,15 @@ data class TextChapter(
* @return 根据索引位置获取所在页 * @return 根据索引位置获取所在页
*/ */
fun getPageIndexByCharIndex(charIndex: Int): Int { 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 var length = 0
for (i in pages.indices) { for (i in pages.indices) {
val page = pages[i] val page = pages[i]
@@ -194,6 +208,7 @@ data class TextChapter(
} }
} }
return pages.lastIndex return pages.lastIndex
*/
} }
fun clearSearchResult() { fun clearSearchResult() {
@@ -264,7 +264,7 @@ data class TextPage(
} }
fun draw(view: ContentTextView, canvas: Canvas?) { fun draw(view: ContentTextView, canvas: Canvas?) {
pictureMirror.draw(canvas, view.width, height.toInt()) { pictureMirror.drawLocked(canvas, view.width, height.toInt(), view) {
drawPage(view, this) drawPage(view, this)
} }
} }
@@ -44,7 +44,6 @@ class TextPageFactory(dataSource: DataSource) : PageFactory<TextPage>(dataSource
ReadBook.setPageIndex(pageIndex.plus(1)) ReadBook.setPageIndex(pageIndex.plus(1))
} }
if (upContent) upContent(resetPageOffset = false) if (upContent) upContent(resetPageOffset = false)
dataSource.onPageChange()
true true
} else } else
false false
@@ -64,7 +63,6 @@ class TextPageFactory(dataSource: DataSource) : PageFactory<TextPage>(dataSource
ReadBook.setPageIndex(pageIndex.minus(1)) ReadBook.setPageIndex(pageIndex.minus(1))
} }
if (upContent) upContent(resetPageOffset = false) if (upContent) upContent(resetPageOffset = false)
dataSource.onPageChange()
true true
} else } else
false false
@@ -7,3 +7,33 @@ fun List<Float>.fastSum(): Float {
} }
return sum 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) }
@@ -3,6 +3,7 @@ package io.legado.app.utils
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Picture import android.graphics.Picture
import android.os.Build import android.os.Build
import android.view.View
import androidx.core.graphics.record import androidx.core.graphics.record
import java.util.concurrent.locks.ReentrantLock import java.util.concurrent.locks.ReentrantLock
@@ -12,16 +13,31 @@ class PictureMirror {
@Volatile @Volatile
var isDirty = true var isDirty = true
val lock = ReentrantLock() 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 (atLeastApi23) {
if (picture == null) picture = Picture() if (picture == null) picture = Picture()
val picture = picture!! val picture = picture!!
if (isDirty) { if (isDirty) {
if (!lock.tryLock()) return if (!lock.tryLock()) {
if (canvas != null && view != null) {
scheduleInvalidateView = view
}
return
}
try { try {
picture.record(width, height, block) picture.record(width, height, block)
isDirty = false isDirty = false
scheduleInvalidateView?.postInvalidate()
scheduleInvalidateView = null
} finally { } finally {
lock.unlock() 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() { fun invalidate() {
isDirty = true isDirty = true
} }