Horis 2 лет назад
Родитель
Сommit
e5148d1a05

+ 2 - 13
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<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> {

+ 8 - 2
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()
         }
     }

+ 7 - 17
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)
     }

+ 1 - 6
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()
     }

+ 0 - 1
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()
 }

+ 15 - 0
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() {

+ 1 - 1
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)
         }
     }

+ 0 - 2
app/src/main/java/io/legado/app/ui/book/read/page/provider/TextPageFactory.kt

@@ -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

+ 30 - 0
app/src/main/java/io/legado/app/utils/CollectionExtensions.kt

@@ -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) }

+ 40 - 2
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
     }