From 5f01b4d5f8487f3c0641d49a55d422abcb294700 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B4=BA=E4=BC=9F=E5=AE=97?= Date: Tue, 24 May 2022 11:38:18 +0800 Subject: [PATCH 1/3] =?UTF-8?q?[memory=20opt]=E5=A4=9A=E4=B8=AAimageKey?= =?UTF-8?q?=E5=AF=B9=E5=BA=94=E5=90=8C=E4=B8=80=E5=BC=A0=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E6=97=B6,=E9=81=BF=E5=85=8D=E9=87=8D=E5=A4=8D=E5=88=9B?= =?UTF-8?q?=E5=BB=BAbitmap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/opensource/svgaplayer/SVGAVideoEntity.kt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/library/src/main/java/com/opensource/svgaplayer/SVGAVideoEntity.kt b/library/src/main/java/com/opensource/svgaplayer/SVGAVideoEntity.kt index 49cb830..a8fdc2c 100644 --- a/library/src/main/java/com/opensource/svgaplayer/SVGAVideoEntity.kt +++ b/library/src/main/java/com/opensource/svgaplayer/SVGAVideoEntity.kt @@ -45,6 +45,7 @@ class SVGAVideoEntity { internal var soundPool: SoundPool? = null private var soundCallback: SVGASoundManager.SVGASoundCallBack? = null internal var imageMap = HashMap() + private val bitmapsMap = mutableMapOf()//图片文件路径:bitmap private var mCacheDir: File private var mFrameHeight = 0 private var mFrameWidth = 0 @@ -146,7 +147,14 @@ class SVGAVideoEntity { } private fun createBitmap(filePath: String): Bitmap? { - return SVGABitmapFileDecoder.decodeBitmapFrom(filePath, mFrameWidth, mFrameHeight) + // 问题:多个imageKey对应同一张图片时,这里却创建了多个bitmap,解压Rocket.svga查看可知 + // 这里的bitmapsMap就是为了避免重复创建相同的bitmap + if (bitmapsMap.containsKey(filePath)) { + return bitmapsMap[filePath] + } + return SVGABitmapFileDecoder.decodeBitmapFrom(filePath, mFrameWidth, mFrameHeight)?.apply { + bitmapsMap[filePath] = this + } } private fun parserImages(obj: MovieEntity) { @@ -341,6 +349,7 @@ class SVGAVideoEntity { soundPool = null audioList = emptyList() spriteList = emptyList() + bitmapsMap.clear() imageMap.clear() } } From 1e629884cacab74ba3cb06445c6d924ade04c096 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B4=BA=E4=BC=9F=E5=AE=97?= Date: Tue, 24 May 2022 11:45:59 +0800 Subject: [PATCH 2/3] =?UTF-8?q?[memory&gc=20opt]=E7=BB=98=E5=88=B6?= =?UTF-8?q?=E9=81=AE=E7=BD=A9=E6=97=B6=E9=87=8D=E7=94=A8bitmap=E5=92=8Ccan?= =?UTF-8?q?vas=EF=BC=8C=E8=A7=A3=E5=86=B3GC=E5=BC=82=E5=B8=B8=E9=97=AE?= =?UTF-8?q?=E9=A2=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../svgaplayer/drawer/SVGACanvasDrawer.kt | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) 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 42a0fbd..cfc333f 100644 --- a/library/src/main/java/com/opensource/svgaplayer/drawer/SVGACanvasDrawer.kt +++ b/library/src/main/java/com/opensource/svgaplayer/drawer/SVGACanvasDrawer.kt @@ -484,6 +484,8 @@ internal class SVGACanvasDrawer(videoItem: SVGAVideoEntity, val dynamicItem: SVG private val shareMattePaint = Paint() private var shareMatteCanvas: Canvas? = null private var sharedMatteBitmap: Bitmap? = null + private var lastSharedWidth = 0 + private var lastSharedHeight = 0 fun sharedPaint(): Paint { sharedPaint.reset() @@ -520,14 +522,16 @@ internal class SVGACanvasDrawer(videoItem: SVGAVideoEntity, val dynamicItem: SVG } fun shareMatteCanvas(width: Int, height: Int): Canvas { - if (shareMatteCanvas == null) { + //避免每帧都重建bitmap和canvas + if (shareMatteCanvas == null || width != lastSharedWidth || height != lastSharedHeight) { sharedMatteBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ALPHA_8) -// shareMatteCanvas = Canvas(sharedMatteBitmap) + shareMatteCanvas = Canvas(sharedMatteBitmap!!) + lastSharedWidth = width + lastSharedHeight = height + } else { + shareMatteCanvas!!.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR) } -// val matteCanvas = shareMatteCanvas as Canvas -// matteCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR) -// return matteCanvas - return Canvas(sharedMatteBitmap) + return shareMatteCanvas!! } } From d393923612149fe26355aae94f9904aaec17b324 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B4=BA=E4=BC=9F=E5=AE=97?= Date: Tue, 24 May 2022 13:09:09 +0800 Subject: [PATCH 3/3] =?UTF-8?q?[fix]=E6=92=AD=E6=94=BE=E9=9F=B3=E9=A2=91?= =?UTF-8?q?=E5=BC=95=E8=B5=B7=E7=9A=84=E5=86=85=E5=AD=98=E6=B3=84=E9=9C=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/opensource/svgaplayer/SVGAParser.kt | 10 +- .../opensource/svgaplayer/SVGAVideoEntity.kt | 158 +++++++----------- 2 files changed, 70 insertions(+), 98 deletions(-) diff --git a/library/src/main/java/com/opensource/svgaplayer/SVGAParser.kt b/library/src/main/java/com/opensource/svgaplayer/SVGAParser.kt index 2b711ef..4dadc83 100644 --- a/library/src/main/java/com/opensource/svgaplayer/SVGAParser.kt +++ b/library/src/main/java/com/opensource/svgaplayer/SVGAParser.kt @@ -225,14 +225,15 @@ class SVGAParser(context: Context?) { LogUtils.info(TAG, "inflate start") inflate(bytes)?.let { LogUtils.info(TAG, "inflate complete") + val movieEntity = MovieEntity.ADAPTER.decode(it) val videoItem = SVGAVideoEntity( - MovieEntity.ADAPTER.decode(it), + movieEntity, File(cacheKey), mFrameWidth, mFrameHeight ) LogUtils.info(TAG, "SVGAVideoEntity prepare start") - videoItem.prepare({ + videoItem.prepare(movieEntity, { LogUtils.info(TAG, "SVGAVideoEntity prepare success") this.invokeCompleteCallback(videoItem, callback, alias) },playCallback) @@ -307,14 +308,15 @@ class SVGAParser(context: Context?) { LogUtils.info(TAG, "inflate start") inflate(bytes)?.let { LogUtils.info(TAG, "inflate complete") + val movieEntity = MovieEntity.ADAPTER.decode(it) val videoItem = SVGAVideoEntity( - MovieEntity.ADAPTER.decode(it), + movieEntity, File(cacheKey), mFrameWidth, mFrameHeight ) LogUtils.info(TAG, "SVGAVideoEntity prepare start") - videoItem.prepare({ + videoItem.prepare(movieEntity, { LogUtils.info(TAG, "SVGAVideoEntity prepare success") this.invokeCompleteCallback(videoItem, callback, alias) },playCallback) diff --git a/library/src/main/java/com/opensource/svgaplayer/SVGAVideoEntity.kt b/library/src/main/java/com/opensource/svgaplayer/SVGAVideoEntity.kt index a8fdc2c..87dbd41 100644 --- a/library/src/main/java/com/opensource/svgaplayer/SVGAVideoEntity.kt +++ b/library/src/main/java/com/opensource/svgaplayer/SVGAVideoEntity.kt @@ -29,7 +29,6 @@ class SVGAVideoEntity { private val TAG = "SVGAVideoEntity" var antiAlias = true - var movieItem: MovieEntity? = null var videoSize = SVGARect(0.0, 0.0, 0.0, 0.0) private set @@ -86,7 +85,6 @@ class SVGAVideoEntity { this.mFrameWidth = frameWidth this.mFrameHeight = frameHeight this.mCacheDir = cacheDir - this.movieItem = entity entity.params?.let(this::setupByMovie) try { parserImages(entity) @@ -106,7 +104,7 @@ class SVGAVideoEntity { frames = movieParams.frames ?: 0 } - internal fun prepare(callback: () -> Unit, playCallback: SVGAParser.PlayCallback?) { + internal fun prepare(movieItem: MovieEntity?,callback: () -> Unit, playCallback: SVGAParser.PlayCallback?) { mCallback = callback mPlayCallback = playCallback if (movieItem == null) { @@ -197,102 +195,74 @@ class SVGAVideoEntity { } ?: listOf() } + private var loadAudioCnt = 0 + private var setupAudioFinished = false + private fun setupAudios(entity: MovieEntity, completionBlock: () -> Unit) { if (entity.audios == null || entity.audios.isEmpty()) { run(completionBlock) return } - setupSoundPool(entity, completionBlock) - val audiosFileMap = generateAudioFileMap(entity) - //repair when audioEntity error can not callback - //如果audiosFileMap为空 soundPool?.load 不会走 导致 setOnLoadCompleteListener 不会回调 导致外层prepare不回调卡住 - if (audiosFileMap.size == 0) { - run(completionBlock) - return - } + loadAudioCnt = 0 + setupAudioFinished = false + //改成count,解决MovieEntity泄露问题 + setupSoundPool(entity.audios.count(), completionBlock) this.audioList = entity.audios.map { audio -> - return@map createSvgaAudioEntity(audio, audiosFileMap) - } - } - - private fun createSvgaAudioEntity(audio: AudioEntity, audiosFileMap: HashMap): SVGAAudioEntity { - val item = SVGAAudioEntity(audio) - val startTime = (audio.startTime ?: 0).toDouble() - val totalTime = (audio.totalTime ?: 0).toDouble() - if (totalTime.toInt() == 0) { - // 除数不能为 0 - return item - } - // 直接回调文件,后续播放都不走 - mPlayCallback?.let { - val fileList: MutableList = ArrayList() - audiosFileMap.forEach { entity -> - fileList.add(entity.value) - } - it.onPlay(fileList) - mCallback() - return item - } - - audiosFileMap[audio.audioKey]?.let { file -> - FileInputStream(file).use { - val length = it.available().toDouble() - val offset = ((startTime / totalTime) * length).toLong() - if (SVGASoundManager.isInit()) { - item.soundID = SVGASoundManager.load(soundCallback, - it.fd, - offset, - length.toLong(), - 1) - } else { - item.soundID = soundPool?.load(it.fd, offset, length.toLong(), 1) + val item = SVGAAudioEntity(audio) + val startTime = (audio.startTime ?: 0).toDouble() + val totalTime = (audio.totalTime ?: 0).toDouble() + if (totalTime.toInt() == 0) { + // 除数不能为 0 + item + } else { + audio.audioKey?.let { imageKey -> + val audioCache = SVGACache.buildAudioFile(imageKey) + if (audioCache.exists()) { + //可能之前已经解过 + } else { + entity.images[imageKey]?.toByteArray()?.apply { + if (count() < 4) return@apply + val fileTag = slice(IntRange(0, 3)) + val isAudioData = + (fileTag[0].toInt() == 73 && fileTag[1].toInt() == 68 && fileTag[2].toInt() == 51) || (fileTag[0].toInt() == -1 && fileTag[1].toInt() == -5 && fileTag[2].toInt() == -108) + if (!isAudioData) return@apply + audioCache.parentFile?.mkdirs() + audioCache.createNewFile() + FileOutputStream(audioCache).write(this) + } + } + if (audioCache.exists()) { + FileInputStream(audioCache).use { + val length = it.available().toDouble() + val offset = ((startTime / totalTime) * length).toLong() + if (SVGASoundManager.isInit()) { + loadAudioCnt++ + item.soundID = SVGASoundManager.load( + soundCallback, + it.fd, + offset, + length.toLong(), + 1 + ) + } else { + item.soundID = soundPool?.load(it.fd, offset, length.toLong(), 1) + if (soundPool != null) { + loadAudioCnt++ + } + } + } + } } + item } } - return item - } - - private fun generateAudioFile(audioCache: File, value: ByteArray): File { - audioCache.createNewFile() - FileOutputStream(audioCache).write(value) - return audioCache - } - - private fun generateAudioFileMap(entity: MovieEntity): HashMap { - val audiosDataMap = generateAudioMap(entity) - val audiosFileMap = HashMap() - if (audiosDataMap.count() > 0) { - audiosDataMap.forEach { - val audioCache = SVGACache.buildAudioFile(it.key) - audiosFileMap[it.key] = - audioCache.takeIf { file -> file.exists() } ?: generateAudioFile( - audioCache, - it.value - ) - } + setupAudioFinished = true + if (loadAudioCnt == 0) { + completionBlock.invoke() } - return audiosFileMap } - private fun generateAudioMap(entity: MovieEntity): HashMap { - val audiosDataMap = HashMap() - entity.images?.entries?.forEach { - val imageKey = it.key - val byteArray = it.value.toByteArray() - if (byteArray.count() < 4) { - return@forEach - } - val fileTag = byteArray.slice(IntRange(0, 3)) - if (fileTag[0].toInt() == 73 && fileTag[1].toInt() == 68 && fileTag[2].toInt() == 51) { - audiosDataMap[imageKey] = byteArray - }else if(fileTag[0].toInt() == -1 && fileTag[1].toInt() == -5 && fileTag[2].toInt() == -108){ - audiosDataMap[imageKey] = byteArray - } - } - return audiosDataMap - } - - private fun setupSoundPool(entity: MovieEntity, completionBlock: () -> Unit) { + private fun setupSoundPool(audioCount: Int, completionBlock: () -> Unit) { var soundLoaded = 0 if (SVGASoundManager.isInit()) { soundCallback = object : SVGASoundManager.SVGASoundCallBack { @@ -302,35 +272,35 @@ class SVGAVideoEntity { override fun onComplete() { soundLoaded++ - if (soundLoaded >= entity.audios.count()) { + if (setupAudioFinished && soundLoaded >= loadAudioCnt) { completionBlock() } } } return } - soundPool = generateSoundPool(entity) + soundPool = generateSoundPool(audioCount) LogUtils.info("SVGAParser", "pool_start") soundPool?.setOnLoadCompleteListener { _, _, _ -> LogUtils.info("SVGAParser", "pool_complete") soundLoaded++ - if (soundLoaded >= entity.audios.count()) { + if (setupAudioFinished && soundLoaded >= loadAudioCnt) { completionBlock() } } } - private fun generateSoundPool(entity: MovieEntity): SoundPool? { + private fun generateSoundPool(audioCount: Int): SoundPool? { return try { if (Build.VERSION.SDK_INT >= 21) { val attributes = AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_MEDIA) .build() SoundPool.Builder().setAudioAttributes(attributes) - .setMaxStreams(12.coerceAtMost(entity.audios.count())) - .build() + .setMaxStreams(12.coerceAtMost(audioCount)) + .build() } else { - SoundPool(12.coerceAtMost(entity.audios.count()), AudioManager.STREAM_MUSIC, 0) + SoundPool(12.coerceAtMost(audioCount), AudioManager.STREAM_MUSIC, 0) } } catch (e: Exception) { LogUtils.error(TAG, e)