diff --git a/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt b/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt index 78fbd70cc..250ac5b51 100644 --- a/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt +++ b/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt @@ -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( 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 async( scope: CoroutineScope = DEFAULT, @@ -51,7 +46,7 @@ class Coroutine( } - private val job: Job by lazy { executeInternal(context, block) } + private val job: Job private var start: VoidCallback? = null private var success: Callback? = null @@ -72,13 +67,7 @@ class Coroutine( 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 { diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt index ac05cbb3b..254b3d720 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt @@ -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() } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt index d80f11816..54002322d 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt @@ -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) } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt index c1814aeb5..0b21995d8 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt @@ -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() } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/api/DataSource.kt b/app/src/main/java/io/legado/app/ui/book/read/page/api/DataSource.kt index e06238554..ceddd279b 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/api/DataSource.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/api/DataSource.kt @@ -21,5 +21,4 @@ interface DataSource { fun upContent(relativePosition: Int = 0, resetPageOffset: Boolean = true) - fun onPageChange() } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChapter.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChapter.kt index 07cff13df..b1f3fb9c6 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChapter.kt @@ -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() { diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt index 079519be1..95d06d8a9 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt @@ -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) } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextPageFactory.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextPageFactory.kt index 386d5d2ed..9ff5f77ee 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextPageFactory.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextPageFactory.kt @@ -44,7 +44,6 @@ class TextPageFactory(dataSource: DataSource) : PageFactory(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(dataSource ReadBook.setPageIndex(pageIndex.minus(1)) } if (upContent) upContent(resetPageOffset = false) - dataSource.onPageChange() true } else false diff --git a/app/src/main/java/io/legado/app/utils/CollectionExtensions.kt b/app/src/main/java/io/legado/app/utils/CollectionExtensions.kt index 3e6f33306..e8e7b87d7 100644 --- a/app/src/main/java/io/legado/app/utils/CollectionExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/CollectionExtensions.kt @@ -7,3 +7,33 @@ fun List.fastSum(): Float { } return sum } + +inline fun List.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 > List.fastBinarySearchBy( + key: K?, + fromIndex: Int = 0, + toIndex: Int = size, + crossinline selector: (T) -> K? +): Int = fastBinarySearch(fromIndex, toIndex) { compareValues(selector(it), key) } diff --git a/app/src/main/java/io/legado/app/utils/PictureMirror.kt b/app/src/main/java/io/legado/app/utils/PictureMirror.kt index 71bd6ccbf..2b03eab20 100644 --- a/app/src/main/java/io/legado/app/utils/PictureMirror.kt +++ b/app/src/main/java/io/legado/app/utils/PictureMirror.kt @@ -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 }