From 7877a37e5cf07dd098b5d0428335d4b863f3c93c Mon Sep 17 00:00:00 2001 From: xxjy Date: Sun, 15 Nov 2020 13:44:38 +0800 Subject: [PATCH] Memory thrashing problem. 1. Avoid repeated creation of SVGADrawerSprite. 2. The replace function leads to the creation of a large number of objects. --- .../svgaplayer/drawer/SGVADrawer.kt | 20 +++- .../svgaplayer/drawer/SVGACanvasDrawer.kt | 7 +- .../com/opensource/svgaplayer/utils/Pools.kt | 102 ++++++++++++++++++ 3 files changed, 124 insertions(+), 5 deletions(-) create mode 100644 library/src/main/java/com/opensource/svgaplayer/utils/Pools.kt diff --git a/library/src/main/java/com/opensource/svgaplayer/drawer/SGVADrawer.kt b/library/src/main/java/com/opensource/svgaplayer/drawer/SGVADrawer.kt index b7e0c7de..93ad3846 100644 --- a/library/src/main/java/com/opensource/svgaplayer/drawer/SGVADrawer.kt +++ b/library/src/main/java/com/opensource/svgaplayer/drawer/SGVADrawer.kt @@ -4,7 +4,9 @@ import android.graphics.Canvas import android.widget.ImageView import com.opensource.svgaplayer.SVGAVideoEntity import com.opensource.svgaplayer.entities.SVGAVideoSpriteFrameEntity +import com.opensource.svgaplayer.utils.Pools import com.opensource.svgaplayer.utils.SVGAScaleInfo +import kotlin.math.max /** * Created by cuiminghui on 2017/3/29. @@ -14,7 +16,13 @@ open internal class SGVADrawer(val videoItem: SVGAVideoEntity) { val scaleInfo = SVGAScaleInfo() - inner class SVGADrawerSprite(val matteKey: String?, val imageKey: String?, val frameEntity: SVGAVideoSpriteFrameEntity) + private val spritePool = Pools.SimplePool(max(1, videoItem.spriteList.size)) + + inner class SVGADrawerSprite(var _matteKey: String? = null, var _imageKey: String? = null, var _frameEntity: SVGAVideoSpriteFrameEntity? = null) { + val matteKey get() = _matteKey + val imageKey get() = _imageKey + val frameEntity get() = _frameEntity!! + } internal fun requestFrameSprites(frameIndex: Int): List { return videoItem.spriteList.mapNotNull { @@ -23,13 +31,21 @@ open internal class SGVADrawer(val videoItem: SVGAVideoEntity) { if (!imageKey.endsWith(".matte") && it.frames[frameIndex].alpha <= 0.0) { return@mapNotNull null } - return@mapNotNull SVGADrawerSprite(it.matteKey, it.imageKey, it.frames[frameIndex]) + return@mapNotNull (spritePool.acquire() ?: SVGADrawerSprite()).apply { + _matteKey = it.matteKey + _imageKey = it.imageKey + _frameEntity = it.frames[frameIndex] + } } } return@mapNotNull null } } + internal fun releaseFrameSprites(sprites: List) { + sprites.forEach { spritePool.release(it) } + } + open fun drawFrame(canvas : Canvas, frameIndex: Int, scaleType: ImageView.ScaleType) { scaleInfo.performScaleType(canvas.width.toFloat(),canvas.height.toFloat(), videoItem.videoSize.width.toFloat(), videoItem.videoSize.height.toFloat(), scaleType) } diff --git a/library/src/main/java/com/opensource/svgaplayer/drawer/SVGACanvasDrawer.kt b/library/src/main/java/com/opensource/svgaplayer/drawer/SVGACanvasDrawer.kt index 7f84c26d..b71c4479 100644 --- a/library/src/main/java/com/opensource/svgaplayer/drawer/SVGACanvasDrawer.kt +++ b/library/src/main/java/com/opensource/svgaplayer/drawer/SVGACanvasDrawer.kt @@ -85,6 +85,7 @@ internal class SVGACanvasDrawer(videoItem: SVGAVideoEntity, val dynamicItem: SVG } } } + releaseFrameSprites(sprites) } private fun isMatteBegin(spriteIndex: Int, sprites: List): Boolean { @@ -101,7 +102,7 @@ internal class SVGACanvasDrawer(videoItem: SVGAVideoEntity, val dynamicItem: SVG svgaDrawerSprite.matteKey?.let { if (it.length > 0) { sprites.get(index - 1)?.let { lastSprite -> - if (lastSprite.matteKey == null || lastSprite.matteKey.length == 0) { + if (lastSprite.matteKey.isNullOrEmpty()) { boolArray[index] = true } else { if (lastSprite.matteKey != svgaDrawerSprite.matteKey) { @@ -135,7 +136,7 @@ internal class SVGACanvasDrawer(videoItem: SVGAVideoEntity, val dynamicItem: SVG boolArray[index] = true } else { sprites.get(index + 1)?.let { nextSprite -> - if (nextSprite.matteKey == null || nextSprite.matteKey.length == 0) { + if (nextSprite.matteKey.isNullOrEmpty()) { boolArray[index] = true } else { if (nextSprite.matteKey != svgaDrawerSprite.matteKey) { @@ -188,7 +189,7 @@ internal class SVGACanvasDrawer(videoItem: SVGAVideoEntity, val dynamicItem: SVG val imageKey = sprite.imageKey ?: return val isHidden = dynamicItem.dynamicHidden[imageKey] == true if (isHidden) { return } - val bitmapKey = imageKey.replace(".matte", "") + val bitmapKey = if (imageKey.endsWith(".matte")) imageKey.substring(0, imageKey.length - 6) else imageKey val drawingBitmap = (dynamicItem.dynamicImage[bitmapKey] ?: videoItem.imageMap[bitmapKey]) ?: return val frameMatrix = shareFrameMatrix(sprite.frameEntity.transform) val paint = this.sharedValues.sharedPaint() diff --git a/library/src/main/java/com/opensource/svgaplayer/utils/Pools.kt b/library/src/main/java/com/opensource/svgaplayer/utils/Pools.kt new file mode 100644 index 00000000..7382ab84 --- /dev/null +++ b/library/src/main/java/com/opensource/svgaplayer/utils/Pools.kt @@ -0,0 +1,102 @@ +package com.opensource.svgaplayer.utils + +/** + * Helper class for creating pools of objects. An example use looks like this: + *
+ * public class MyPooledClass {
+ *
+ *     private static final SynchronizedPool sPool =
+ *             new SynchronizedPool(10);
+ *
+ *     public static MyPooledClass obtain() {
+ *         MyPooledClass instance = sPool.acquire();
+ *         return (instance != null) ? instance : new MyPooledClass();
+ *     }
+ *
+ *     public void recycle() {
+ *          // Clear state if needed.
+ *          sPool.release(this);
+ *     }
+ *
+ *     . . .
+ * }
+ * 
+ * + */ +class Pools private constructor() { + + /** + * Interface for managing a pool of objects. + * + * @param The pooled type. + */ + interface Pool { + /** + * @return An instance from the pool if such, null otherwise. + */ + fun acquire(): T? + + /** + * Release an instance to the pool. + * + * @param instance The instance to release. + * @return Whether the instance was put in the pool. + * + * @throws IllegalStateException If the instance is already in the pool. + */ + fun release(instance: T): Boolean + } + + /** + * Simple (non-synchronized) pool of objects. + * + * @param maxPoolSize The max pool size. + * + * @throws IllegalArgumentException If the max pool size is less than zero. + * + * @param The pooled type. + */ + open class SimplePool(maxPoolSize: Int) : Pool { + private val mPool: Array + private var mPoolSize = 0 + + init { + require(maxPoolSize > 0) { "The max pool size must be > 0" } + mPool = arrayOfNulls(maxPoolSize) + } + + @Suppress("UNCHECKED_CAST") + override fun acquire(): T? { + if (mPoolSize > 0) { + val lastPooledIndex = mPoolSize - 1 + val instance = mPool[lastPooledIndex] as T? + mPool[lastPooledIndex] = null + mPoolSize-- + return instance + } + return null + } + + override fun release(instance: T): Boolean { + check(!isInPool(instance)) { "Already in the pool!" } + if (mPoolSize < mPool.size) { + mPool[mPoolSize] = instance + mPoolSize++ + return true + } + return false + } + + private fun isInPool(instance: T): Boolean { + for (i in 0 until mPoolSize) { + if (mPool[i] === instance) { + return true + } + } + return false + } + + } + + +} \ No newline at end of file