This commit is contained in:
Horis
2024-02-19 22:02:25 +08:00
parent ce2883b75c
commit 8ba16e2560
13 changed files with 206 additions and 16 deletions
@@ -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()
}
@@ -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()
}
}
@@ -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()
}
}
@@ -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()
}
}
@@ -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 {
@@ -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<T> : ObjectPool<T> {
private val pool = LinkedList<SoftReference<T>>()
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))
}
}
@@ -0,0 +1,11 @@
package io.legado.app.utils.canvasrecorder.objectpool
interface ObjectPool<T> {
fun obtain(): T
fun recycle(target: T)
fun create(): T
}
@@ -0,0 +1,3 @@
package io.legado.app.utils.canvasrecorder.objectpool
fun <T> ObjectPool<T>.synchronized(): ObjectPool<T> = ObjectPoolLocked(this)
@@ -0,0 +1,15 @@
package io.legado.app.utils.canvasrecorder.objectpool
class ObjectPoolLocked<T>(private val delegate: ObjectPool<T>) : ObjectPool<T> by delegate {
@Synchronized
override fun obtain(): T {
return delegate.obtain()
}
@Synchronized
override fun recycle(target: T) {
return delegate.recycle(target)
}
}
@@ -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<SoftReference<Bitmap>> = 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
}
}
}
@@ -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<Canvas>(size)
fun obtain(): Canvas {
val canvas = pool.acquire() ?: Canvas()
return canvas
}
fun recycle(canvas: Canvas) {
canvas.setBitmap(null)
canvas.restoreToCount(1)
pool.release(canvas)
}
}
@@ -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<Picture>() {
override fun create(): Picture = Picture()
}
@@ -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<RenderNode>() {
override fun recycle(target: RenderNode) {
target.discardDisplayList()
super.recycle(target)
}
override fun create(): RenderNode = RenderNode("CanvasRecorder")
}