From 8ba16e256087d99e6521105da1fc63423d41aae0 Mon Sep 17 00:00:00 2001 From: Horis <821938089@qq.com> Date: Mon, 19 Feb 2024 22:02:25 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/ui/book/read/page/ContentTextView.kt | 4 +- .../canvasrecorder/CanvasRecorderApi23Impl.kt | 10 ++- .../canvasrecorder/CanvasRecorderApi29Impl.kt | 16 ++++- .../canvasrecorder/CanvasRecorderImpl.kt | 25 +++++--- .../canvasrecorder/CanvasRecorderLocked.kt | 2 +- .../objectpool/BaseObjectPool.kt | 24 +++++++ .../canvasrecorder/objectpool/ObjectPool.kt | 11 ++++ .../objectpool/ObjectPoolExtensions.kt | 3 + .../objectpool/ObjectPoolLocked.kt | 15 +++++ .../utils/canvasrecorder/pools/BitmapPool.kt | 63 +++++++++++++++++++ .../utils/canvasrecorder/pools/CanvasPool.kt | 21 +++++++ .../utils/canvasrecorder/pools/PicturePool.kt | 10 +++ .../canvasrecorder/pools/RenderNodePool.kt | 18 ++++++ 13 files changed, 206 insertions(+), 16 deletions(-) create mode 100644 app/src/main/java/io/legado/app/utils/canvasrecorder/objectpool/BaseObjectPool.kt create mode 100644 app/src/main/java/io/legado/app/utils/canvasrecorder/objectpool/ObjectPool.kt create mode 100644 app/src/main/java/io/legado/app/utils/canvasrecorder/objectpool/ObjectPoolExtensions.kt create mode 100644 app/src/main/java/io/legado/app/utils/canvasrecorder/objectpool/ObjectPoolLocked.kt create mode 100644 app/src/main/java/io/legado/app/utils/canvasrecorder/pools/BitmapPool.kt create mode 100644 app/src/main/java/io/legado/app/utils/canvasrecorder/pools/CanvasPool.kt create mode 100644 app/src/main/java/io/legado/app/utils/canvasrecorder/pools/PicturePool.kt create mode 100644 app/src/main/java/io/legado/app/utils/canvasrecorder/pools/RenderNodePool.kt 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 50180f174..b01b0570e 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 @@ -81,6 +81,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at } else { invalidate() } + submitRenderTask() } override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { @@ -216,7 +217,6 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at is TextColumn -> { if (!selectAble) return@touch column.selected = true - invalidate() select(textPos) } } @@ -601,7 +601,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at } } } - invalidate() + submitRenderTask() callBack.onCancelSelect() } diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi23Impl.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi23Impl.kt index b23024e85..f9c19cac6 100644 --- a/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi23Impl.kt +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi23Impl.kt @@ -2,6 +2,8 @@ package io.legado.app.utils.canvasrecorder import android.graphics.Canvas import android.graphics.Picture +import io.legado.app.utils.canvasrecorder.objectpool.synchronized +import io.legado.app.utils.canvasrecorder.pools.PicturePool class CanvasRecorderApi23Impl : BaseCanvasRecorder() { @@ -12,7 +14,7 @@ class CanvasRecorderApi23Impl : BaseCanvasRecorder() { private fun initPicture() { if (picture == null) { - picture = Picture() + picture = picturePool.obtain() } } @@ -33,7 +35,13 @@ class CanvasRecorderApi23Impl : BaseCanvasRecorder() { override fun recycle() { super.recycle() + if (picture == null) return + picturePool.recycle(picture!!) picture = null } + companion object { + private val picturePool = PicturePool().synchronized() + } + } diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi29Impl.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi29Impl.kt index d6d7ddc6f..c7bc9ceb8 100644 --- a/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi29Impl.kt +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi29Impl.kt @@ -5,6 +5,9 @@ import android.graphics.Picture import android.graphics.RenderNode import android.os.Build import androidx.annotation.RequiresApi +import io.legado.app.utils.canvasrecorder.objectpool.synchronized +import io.legado.app.utils.canvasrecorder.pools.PicturePool +import io.legado.app.utils.canvasrecorder.pools.RenderNodePool @RequiresApi(Build.VERSION_CODES.Q) class CanvasRecorderApi29Impl : BaseCanvasRecorder() { @@ -17,10 +20,10 @@ class CanvasRecorderApi29Impl : BaseCanvasRecorder() { private fun init() { if (renderNode == null) { - renderNode = RenderNode("CanvasRecorder") + renderNode = renderNodePool.obtain() } if (picture == null) { - picture = Picture() + picture = picturePool.obtain() } } @@ -56,9 +59,16 @@ class CanvasRecorderApi29Impl : BaseCanvasRecorder() { override fun recycle() { super.recycle() - renderNode?.discardDisplayList() + if (renderNode == null || picture == null) return + renderNodePool.recycle(renderNode!!) renderNode = null + picturePool.recycle(picture!!) picture = null } + companion object { + private val picturePool = PicturePool().synchronized() + private val renderNodePool = RenderNodePool().synchronized() + } + } diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderImpl.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderImpl.kt index 40ad53151..3cd205292 100644 --- a/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderImpl.kt +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderImpl.kt @@ -3,6 +3,8 @@ package io.legado.app.utils.canvasrecorder import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Color +import io.legado.app.utils.canvasrecorder.pools.BitmapPool +import io.legado.app.utils.canvasrecorder.pools.CanvasPool class CanvasRecorderImpl : BaseCanvasRecorder() { @@ -14,19 +16,15 @@ class CanvasRecorderImpl : BaseCanvasRecorder() { private fun init(width: Int, height: Int) { if (bitmap == null) { - bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) - } - if (canvas == null) { - canvas = Canvas(bitmap!!) + bitmap = bitmapPool.obtain(width, height) } if (bitmap!!.width != width || bitmap!!.height != height) { if (canReconfigure(width, height)) { bitmap!!.reconfigure(width, height, Bitmap.Config.ARGB_8888) } else { - bitmap!!.recycle() - bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + bitmapPool.recycle(bitmap!!) + bitmap = bitmapPool.obtain(width, height) } - canvas!!.setBitmap(bitmap!!) } } @@ -37,12 +35,15 @@ class CanvasRecorderImpl : BaseCanvasRecorder() { override fun beginRecording(width: Int, height: Int): Canvas { init(width, height) bitmap!!.eraseColor(Color.TRANSPARENT) + canvas = canvasPool.obtain().apply { setBitmap(bitmap!!) } return canvas!! } override fun endRecording() { bitmap!!.prepareToDraw() super.endRecording() + canvasPool.recycle(canvas!!) + canvas = null } override fun draw(canvas: Canvas) { @@ -52,8 +53,14 @@ class CanvasRecorderImpl : BaseCanvasRecorder() { override fun recycle() { super.recycle() - bitmap?.recycle() - bitmap = null + val bitmap = bitmap ?: return + bitmapPool.recycle(bitmap) + this.bitmap = null + } + + companion object { + private val canvasPool = CanvasPool(2) + private val bitmapPool = BitmapPool() } } diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderLocked.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderLocked.kt index 993491abd..46c91f17e 100644 --- a/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderLocked.kt +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderLocked.kt @@ -31,7 +31,7 @@ class CanvasRecorderLocked(private val delegate: CanvasRecorder) : override fun draw(canvas: Canvas) { if (lock == null) return - if (!lock!!.tryLock()) return + lock!!.lock() try { delegate.draw(canvas) } finally { diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/objectpool/BaseObjectPool.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/objectpool/BaseObjectPool.kt new file mode 100644 index 000000000..59b7410de --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/objectpool/BaseObjectPool.kt @@ -0,0 +1,24 @@ +package io.legado.app.utils.canvasrecorder.objectpool + +import androidx.annotation.CallSuper +import java.lang.ref.SoftReference +import java.util.LinkedList + +abstract class BaseObjectPool : ObjectPool { + + private val pool = LinkedList>() + + override fun obtain(): T { + while (true) { + if (pool.isEmpty()) break + return pool.poll()?.get() ?: continue + } + return create() + } + + @CallSuper + override fun recycle(target: T) { + pool.add(SoftReference(target)) + } + +} diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/objectpool/ObjectPool.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/objectpool/ObjectPool.kt new file mode 100644 index 000000000..71fd824c6 --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/objectpool/ObjectPool.kt @@ -0,0 +1,11 @@ +package io.legado.app.utils.canvasrecorder.objectpool + +interface ObjectPool { + + fun obtain(): T + + fun recycle(target: T) + + fun create(): T + +} diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/objectpool/ObjectPoolExtensions.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/objectpool/ObjectPoolExtensions.kt new file mode 100644 index 000000000..c623ddee6 --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/objectpool/ObjectPoolExtensions.kt @@ -0,0 +1,3 @@ +package io.legado.app.utils.canvasrecorder.objectpool + +fun ObjectPool.synchronized(): ObjectPool = ObjectPoolLocked(this) diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/objectpool/ObjectPoolLocked.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/objectpool/ObjectPoolLocked.kt new file mode 100644 index 000000000..893ac7731 --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/objectpool/ObjectPoolLocked.kt @@ -0,0 +1,15 @@ +package io.legado.app.utils.canvasrecorder.objectpool + +class ObjectPoolLocked(private val delegate: ObjectPool) : ObjectPool by delegate { + + @Synchronized + override fun obtain(): T { + return delegate.obtain() + } + + @Synchronized + override fun recycle(target: T) { + return delegate.recycle(target) + } + +} diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/pools/BitmapPool.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/pools/BitmapPool.kt new file mode 100644 index 000000000..62c5845e3 --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/pools/BitmapPool.kt @@ -0,0 +1,63 @@ +package io.legado.app.utils.canvasrecorder.pools + +import android.graphics.Bitmap +import java.lang.ref.SoftReference +import java.util.concurrent.ConcurrentHashMap + +class BitmapPool { + + private val reusableBitmaps: MutableSet> = ConcurrentHashMap.newKeySet() + + fun recycle(bitmap: Bitmap) { + reusableBitmaps.add(SoftReference(bitmap)) + } + + fun obtain(width: Int, height: Int): Bitmap { + if (reusableBitmaps.isEmpty()) { + return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + } + val iterator = reusableBitmaps.iterator() + while (iterator.hasNext()) { + val item = iterator.next().get() ?: continue + if (item.isMutable) { + // Check to see it the item can be used for inBitmap. + if (canReconfigure(item, width, height)) { + // Remove from reusable set so it can't be used again. + iterator.remove() + item.reconfigure(width, height, Bitmap.Config.ARGB_8888) + return item + } + } else { + // Remove from the set if the reference has been cleared. + iterator.remove() + } + } + return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + } + + private fun canReconfigure( + candidate: Bitmap, + width: Int, + height: Int + ): Boolean { + // From Android 4.4 (KitKat) onward we can re-use if the byte size of + // the new bitmap is smaller than the reusable bitmap candidate + // allocation byte count. + val byteCount: Int = width * height * getBytesPerPixel(candidate.config) + return byteCount <= candidate.allocationByteCount + } + + + /** + * A helper function to return the byte usage per pixel of a bitmap based on its configuration. + */ + private fun getBytesPerPixel(config: Bitmap.Config): Int { + return when (config) { + Bitmap.Config.ARGB_8888 -> 4 + Bitmap.Config.RGB_565, Bitmap.Config.ARGB_4444 -> 2 + Bitmap.Config.ALPHA_8 -> 1 + else -> 1 + } + } + +} diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/pools/CanvasPool.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/pools/CanvasPool.kt new file mode 100644 index 000000000..2f3d625e7 --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/pools/CanvasPool.kt @@ -0,0 +1,21 @@ +package io.legado.app.utils.canvasrecorder.pools + +import android.graphics.Canvas +import androidx.core.util.Pools + +class CanvasPool(size: Int) { + + private val pool = Pools.SynchronizedPool(size) + + fun obtain(): Canvas { + val canvas = pool.acquire() ?: Canvas() + return canvas + } + + fun recycle(canvas: Canvas) { + canvas.setBitmap(null) + canvas.restoreToCount(1) + pool.release(canvas) + } + +} diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/pools/PicturePool.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/pools/PicturePool.kt new file mode 100644 index 000000000..ca0f25572 --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/pools/PicturePool.kt @@ -0,0 +1,10 @@ +package io.legado.app.utils.canvasrecorder.pools + +import android.graphics.Picture +import io.legado.app.utils.canvasrecorder.objectpool.BaseObjectPool + +class PicturePool : BaseObjectPool() { + + override fun create(): Picture = Picture() + +} diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/pools/RenderNodePool.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/pools/RenderNodePool.kt new file mode 100644 index 000000000..3cf1fda2b --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/pools/RenderNodePool.kt @@ -0,0 +1,18 @@ +package io.legado.app.utils.canvasrecorder.pools + +import android.graphics.RenderNode +import android.os.Build +import androidx.annotation.RequiresApi +import io.legado.app.utils.canvasrecorder.objectpool.BaseObjectPool + +@RequiresApi(Build.VERSION_CODES.Q) +class RenderNodePool : BaseObjectPool() { + + override fun recycle(target: RenderNode) { + target.discardDisplayList() + super.recycle(target) + } + + override fun create(): RenderNode = RenderNode("CanvasRecorder") + +}