Skip to content

Commit 7877a37

Browse files
committed
Memory thrashing problem.
1. Avoid repeated creation of SVGADrawerSprite. 2. The replace function leads to the creation of a large number of objects.
1 parent a8bcaf2 commit 7877a37

File tree

3 files changed

+124
-5
lines changed

3 files changed

+124
-5
lines changed

library/src/main/java/com/opensource/svgaplayer/drawer/SGVADrawer.kt

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import android.graphics.Canvas
44
import android.widget.ImageView
55
import com.opensource.svgaplayer.SVGAVideoEntity
66
import com.opensource.svgaplayer.entities.SVGAVideoSpriteFrameEntity
7+
import com.opensource.svgaplayer.utils.Pools
78
import com.opensource.svgaplayer.utils.SVGAScaleInfo
9+
import kotlin.math.max
810

911
/**
1012
* Created by cuiminghui on 2017/3/29.
@@ -14,7 +16,13 @@ open internal class SGVADrawer(val videoItem: SVGAVideoEntity) {
1416

1517
val scaleInfo = SVGAScaleInfo()
1618

17-
inner class SVGADrawerSprite(val matteKey: String?, val imageKey: String?, val frameEntity: SVGAVideoSpriteFrameEntity)
19+
private val spritePool = Pools.SimplePool<SVGADrawerSprite>(max(1, videoItem.spriteList.size))
20+
21+
inner class SVGADrawerSprite(var _matteKey: String? = null, var _imageKey: String? = null, var _frameEntity: SVGAVideoSpriteFrameEntity? = null) {
22+
val matteKey get() = _matteKey
23+
val imageKey get() = _imageKey
24+
val frameEntity get() = _frameEntity!!
25+
}
1826

1927
internal fun requestFrameSprites(frameIndex: Int): List<SVGADrawerSprite> {
2028
return videoItem.spriteList.mapNotNull {
@@ -23,13 +31,21 @@ open internal class SGVADrawer(val videoItem: SVGAVideoEntity) {
2331
if (!imageKey.endsWith(".matte") && it.frames[frameIndex].alpha <= 0.0) {
2432
return@mapNotNull null
2533
}
26-
return@mapNotNull SVGADrawerSprite(it.matteKey, it.imageKey, it.frames[frameIndex])
34+
return@mapNotNull (spritePool.acquire() ?: SVGADrawerSprite()).apply {
35+
_matteKey = it.matteKey
36+
_imageKey = it.imageKey
37+
_frameEntity = it.frames[frameIndex]
38+
}
2739
}
2840
}
2941
return@mapNotNull null
3042
}
3143
}
3244

45+
internal fun releaseFrameSprites(sprites: List<SVGADrawerSprite>) {
46+
sprites.forEach { spritePool.release(it) }
47+
}
48+
3349
open fun drawFrame(canvas : Canvas, frameIndex: Int, scaleType: ImageView.ScaleType) {
3450
scaleInfo.performScaleType(canvas.width.toFloat(),canvas.height.toFloat(), videoItem.videoSize.width.toFloat(), videoItem.videoSize.height.toFloat(), scaleType)
3551
}

library/src/main/java/com/opensource/svgaplayer/drawer/SVGACanvasDrawer.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ internal class SVGACanvasDrawer(videoItem: SVGAVideoEntity, val dynamicItem: SVG
8585
}
8686
}
8787
}
88+
releaseFrameSprites(sprites)
8889
}
8990

9091
private fun isMatteBegin(spriteIndex: Int, sprites: List<SVGADrawerSprite>): Boolean {
@@ -101,7 +102,7 @@ internal class SVGACanvasDrawer(videoItem: SVGAVideoEntity, val dynamicItem: SVG
101102
svgaDrawerSprite.matteKey?.let {
102103
if (it.length > 0) {
103104
sprites.get(index - 1)?.let { lastSprite ->
104-
if (lastSprite.matteKey == null || lastSprite.matteKey.length == 0) {
105+
if (lastSprite.matteKey.isNullOrEmpty()) {
105106
boolArray[index] = true
106107
} else {
107108
if (lastSprite.matteKey != svgaDrawerSprite.matteKey) {
@@ -135,7 +136,7 @@ internal class SVGACanvasDrawer(videoItem: SVGAVideoEntity, val dynamicItem: SVG
135136
boolArray[index] = true
136137
} else {
137138
sprites.get(index + 1)?.let { nextSprite ->
138-
if (nextSprite.matteKey == null || nextSprite.matteKey.length == 0) {
139+
if (nextSprite.matteKey.isNullOrEmpty()) {
139140
boolArray[index] = true
140141
} else {
141142
if (nextSprite.matteKey != svgaDrawerSprite.matteKey) {
@@ -188,7 +189,7 @@ internal class SVGACanvasDrawer(videoItem: SVGAVideoEntity, val dynamicItem: SVG
188189
val imageKey = sprite.imageKey ?: return
189190
val isHidden = dynamicItem.dynamicHidden[imageKey] == true
190191
if (isHidden) { return }
191-
val bitmapKey = imageKey.replace(".matte", "")
192+
val bitmapKey = if (imageKey.endsWith(".matte")) imageKey.substring(0, imageKey.length - 6) else imageKey
192193
val drawingBitmap = (dynamicItem.dynamicImage[bitmapKey] ?: videoItem.imageMap[bitmapKey]) ?: return
193194
val frameMatrix = shareFrameMatrix(sprite.frameEntity.transform)
194195
val paint = this.sharedValues.sharedPaint()
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package com.opensource.svgaplayer.utils
2+
3+
/**
4+
* Helper class for creating pools of objects. An example use looks like this:
5+
* <pre>
6+
* public class MyPooledClass {
7+
*
8+
* private static final SynchronizedPool<MyPooledClass> sPool =
9+
* new SynchronizedPool<MyPooledClass>(10);
10+
*
11+
* public static MyPooledClass obtain() {
12+
* MyPooledClass instance = sPool.acquire();
13+
* return (instance != null) ? instance : new MyPooledClass();
14+
* }
15+
*
16+
* public void recycle() {
17+
* // Clear state if needed.
18+
* sPool.release(this);
19+
* }
20+
*
21+
* . . .
22+
* }
23+
* </pre>
24+
*
25+
*/
26+
class Pools private constructor() {
27+
28+
/**
29+
* Interface for managing a pool of objects.
30+
*
31+
* @param <T> The pooled type.
32+
*/
33+
interface Pool<T> {
34+
/**
35+
* @return An instance from the pool if such, null otherwise.
36+
*/
37+
fun acquire(): T?
38+
39+
/**
40+
* Release an instance to the pool.
41+
*
42+
* @param instance The instance to release.
43+
* @return Whether the instance was put in the pool.
44+
*
45+
* @throws IllegalStateException If the instance is already in the pool.
46+
*/
47+
fun release(instance: T): Boolean
48+
}
49+
50+
/**
51+
* Simple (non-synchronized) pool of objects.
52+
*
53+
* @param maxPoolSize The max pool size.
54+
*
55+
* @throws IllegalArgumentException If the max pool size is less than zero.
56+
*
57+
* @param <T> The pooled type.
58+
*/
59+
open class SimplePool<T>(maxPoolSize: Int) : Pool<T> {
60+
private val mPool: Array<Any?>
61+
private var mPoolSize = 0
62+
63+
init {
64+
require(maxPoolSize > 0) { "The max pool size must be > 0" }
65+
mPool = arrayOfNulls(maxPoolSize)
66+
}
67+
68+
@Suppress("UNCHECKED_CAST")
69+
override fun acquire(): T? {
70+
if (mPoolSize > 0) {
71+
val lastPooledIndex = mPoolSize - 1
72+
val instance = mPool[lastPooledIndex] as T?
73+
mPool[lastPooledIndex] = null
74+
mPoolSize--
75+
return instance
76+
}
77+
return null
78+
}
79+
80+
override fun release(instance: T): Boolean {
81+
check(!isInPool(instance)) { "Already in the pool!" }
82+
if (mPoolSize < mPool.size) {
83+
mPool[mPoolSize] = instance
84+
mPoolSize++
85+
return true
86+
}
87+
return false
88+
}
89+
90+
private fun isInPool(instance: T): Boolean {
91+
for (i in 0 until mPoolSize) {
92+
if (mPool[i] === instance) {
93+
return true
94+
}
95+
}
96+
return false
97+
}
98+
99+
}
100+
101+
102+
}

0 commit comments

Comments
 (0)