Skip to content
This repository has been archived by the owner on Feb 6, 2023. It is now read-only.

Commit

Permalink
* 1 SVGAParser.kt 添加缓存SVGA文件到本地的扩展
Browse files Browse the repository at this point in the history
  2 SVGADrawable.kt SVGAImageView.kt SVGAVideoEntity.kt 公开clear()方法.
  3 SVGAVideoEntity.kt generateAudioFileMap 方法每次打开同一个文件会解压一次mp3.缓存文件会越来越大.改为固定名字.
  • Loading branch information
linjp committed Aug 25, 2020
1 parent 88d7296 commit 84fcf55
Show file tree
Hide file tree
Showing 7 changed files with 219 additions and 48 deletions.
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
ext.kotlin_version = '1.3.11'
ext.kotlin_version = '1.3.50'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.0'
classpath 'com.android.tools.build:gradle:4.0.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

// NOTE: Do not place your application dependencies here; they belong
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
63 changes: 63 additions & 0 deletions library/src/main/java/com/opensource/svgaplayer/SVGACache.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.opensource.svgaplayer

import android.content.Context
import java.io.File
import java.net.URL
import java.security.MessageDigest


object SVGACache {
enum class Type {
DEFAULT,
FILE
}

private var type: Type = Type.DEFAULT
private var cacheDir: String = "/"

fun onCreate(context: Context?) {
onCreate(context, Type.DEFAULT)
}

fun onCreate(context: Context?, type: Type) {
if (isInitialized()) return
context ?: return
cacheDir = "${context.cacheDir.absolutePath}/svga/"
File(cacheDir).takeIf { !it.exists() }?.mkdir()
this.type = type
}

fun isInitialized(): Boolean {
return "/" != cacheDir
}

fun isDefaultCache(): Boolean = type == Type.DEFAULT

fun isCached(cacheKey: String): Boolean {
return (if (isDefaultCache()) buildCacheDir(cacheKey) else buildCacheFile(
cacheKey
)).exists()
}

fun buildCacheKey(str: String): String {
val messageDigest = MessageDigest.getInstance("MD5")
messageDigest.update(str.toByteArray(charset("UTF-8")))
val digest = messageDigest.digest()
var sb = ""
for (b in digest) {
sb += String.format("%02x", b)
}
return sb
}

fun buildCacheKey(url: URL): String = buildCacheKey(url.toString())

fun buildCacheDir(cacheKey: String): File {
return File("$cacheDir$cacheKey/")
}

fun buildCacheFile(cacheKey: String): File {
return File("$cacheDir$cacheKey.svga")
}

}
26 changes: 25 additions & 1 deletion library/src/main/java/com/opensource/svgaplayer/SVGADrawable.kt
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,31 @@ class SVGADrawable(val videoItem: SVGAVideoEntity, val dynamicItem: SVGADynamicE

}

internal fun clear() {
fun resume() {
videoItem.audioList.forEach { audio ->
audio.playID?.let {
videoItem.soundPool?.resume(it)
}
}
}

fun pause() {
videoItem.audioList.forEach { audio ->
audio.playID?.let {
videoItem.soundPool?.pause(it)
}
}
}

fun stop() {
videoItem.audioList.forEach { audio ->
audio.playID?.let {
videoItem.soundPool?.stop(it)
}
}
}

fun clear() {
videoItem.audioList.forEach { audio ->
audio.playID?.let {
videoItem.soundPool?.stop(it)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ open class SVGAImageView @JvmOverloads constructor(context: Context, attrs: Attr

var loops = 0
var clearsAfterStop = true
var clearsAfterDetached = true
var fillMode: FillMode = FillMode.Forward
var callback: SVGACallback? = null

Expand Down Expand Up @@ -187,7 +188,7 @@ open class SVGAImageView @JvmOverloads constructor(context: Context, attrs: Attr
callback?.onFinished()
}

private fun clear() {
fun clear() {
getSVGADrawable()?.cleared = true
getSVGADrawable()?.clear()
// 清除对 drawable 的引用
Expand Down Expand Up @@ -270,7 +271,9 @@ open class SVGAImageView @JvmOverloads constructor(context: Context, attrs: Attr
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
stopAnimation(true)
clear()
if (clearsAfterDetached) {
clear()
}
}

private class AnimatorListener(view: SVGAImageView) : Animator.AnimatorListener {
Expand Down
145 changes: 110 additions & 35 deletions library/src/main/java/com/opensource/svgaplayer/SVGAParser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ private var isUnzipping = false
class SVGAParser(context: Context?) {
private var mContext = context?.applicationContext

init {
SVGACache.onCreate(context)
}

@Volatile
private var mFrameWidth: Int = 0

Expand Down Expand Up @@ -118,6 +122,7 @@ class SVGAParser(context: Context?) {

fun init(context: Context) {
mContext = context.applicationContext
SVGACache.onCreate(mContext)
}

fun setFrameSize(frameWidth: Int, frameHeight: Int) {
Expand All @@ -134,7 +139,7 @@ class SVGAParser(context: Context?) {
LogUtils.info(TAG, "================ decode from assets ================")
threadPoolExecutor.execute {
mContext?.assets?.open(name)?.let {
this.decodeFromInputStream(it, buildCacheKey("file:///assets/$name"), callback, true)
this.decodeFromInputStream(it, SVGACache.buildCacheKey("file:///assets/$name"), callback, true)
}
}
} catch (e: java.lang.Exception) {
Expand All @@ -148,24 +153,102 @@ class SVGAParser(context: Context?) {
return null
}
LogUtils.info(TAG, "================ decode from url ================")
return if (this.isCached(buildCacheKey(url))) {
val cacheKey = SVGACache.buildCacheKey(url);
return if (SVGACache.isCached(cacheKey)) {
LogUtils.info(TAG, "this url cached")
threadPoolExecutor.execute {
this.decodeFromCacheKey(buildCacheKey(url), callback)
if (SVGACache.isDefaultCache()) {
this.decodeFromCacheKey(cacheKey, callback)
} else {
this._decodeFromCacheKey(cacheKey, callback)
}
}
null
}
else {
return null
} else {
LogUtils.info(TAG, "no cached, prepare to download")
fileDownloader.resume(url, {
this.decodeFromInputStream(it, this.buildCacheKey(url), callback)
if (SVGACache.isDefaultCache()) {
this.decodeFromInputStream(it, cacheKey, callback)
} else {
this._decodeFromInputStream(it, cacheKey, callback)
}
}, {
this.invokeErrorCallback(it, callback)
})
}
}

fun decodeFromInputStream(inputStream: InputStream, cacheKey: String, callback: ParseCompletion?, closeInputStream: Boolean = false) {
fun _decodeFromCacheKey(cacheKey: String, callback: ParseCompletion?) {
val svga = SVGACache.buildCacheFile(cacheKey)
try {
LogUtils.info(TAG, "binary change to entity")
FileInputStream(svga).use {
LogUtils.info(TAG, "binary change to entity success")
this.invokeCompleteCallback(
SVGAVideoEntity(
MovieEntity.ADAPTER.decode(it),
SVGACache.buildCacheDir(cacheKey),
mFrameWidth,
mFrameHeight
), callback
)
}
} catch (e: Exception) {
LogUtils.error(TAG, "binary change to entity fail", e)
SVGACache.buildCacheDir(cacheKey).delete()
svga.delete()
this.invokeErrorCallback(e, callback)
}
}

fun _decodeFromInputStream(
inputStream: InputStream,
cacheKey: String,
callback: ParseCompletion?
) {
threadPoolExecutor.execute {
try {
readAsBytes(inputStream)?.let { bytes ->
LogUtils.info(TAG, "decode from input stream, inflate start")
inflate(bytes)?.let { inflateBytes ->
threadPoolExecutor.execute {
SVGACache.buildCacheFile(cacheKey).let { cacheFile ->
cacheFile.takeIf { !it.exists() }?.createNewFile()
FileOutputStream(cacheFile).write(inflateBytes)
}
}
val videoItem = SVGAVideoEntity(
MovieEntity.ADAPTER.decode(inflateBytes),
File(cacheKey),
mFrameWidth,
mFrameHeight
)
videoItem.prepare {
LogUtils.info(TAG, "decode from input stream, inflate end")
this.invokeCompleteCallback(videoItem, callback)
}
} ?: this.invokeErrorCallback(
Exception("inflate(bytes) cause exception"),
callback
)
} ?: this.invokeErrorCallback(
Exception("readAsBytes(inputStream) cause exception"),
callback
)
} catch (e: Exception) {
this.invokeErrorCallback(e, callback)
} finally {
inputStream.close()
}
}
}

fun decodeFromInputStream(
inputStream: InputStream,
cacheKey: String,
callback: ParseCompletion?,
closeInputStream: Boolean = false
) {
if (mContext == null) {
LogUtils.error(TAG, "在配置 SVGAParser context 前, 无法解析 SVGA 文件。")
return
Expand All @@ -176,9 +259,9 @@ class SVGAParser(context: Context?) {
readAsBytes(inputStream)?.let { bytes ->
if (bytes.size > 4 && bytes[0].toInt() == 80 && bytes[1].toInt() == 75 && bytes[2].toInt() == 3 && bytes[3].toInt() == 4) {
LogUtils.info(TAG, "decode from zip file")
if (!buildCacheDir(cacheKey).exists() || isUnzipping) {
if (!SVGACache.buildCacheDir(cacheKey).exists() || isUnzipping) {
synchronized(fileLock) {
if (!buildCacheDir(cacheKey).exists()) {
if (!SVGACache.buildCacheDir(cacheKey).exists()) {
isUnzipping = true
LogUtils.info(TAG, "no cached, prepare to unzip")
ByteArrayInputStream(bytes).use {
Expand All @@ -193,14 +276,26 @@ class SVGAParser(context: Context?) {
} else {
LogUtils.info(TAG, "decode from input stream, inflate start")
inflate(bytes)?.let {
val videoItem = SVGAVideoEntity(MovieEntity.ADAPTER.decode(it), File(cacheKey), mFrameWidth, mFrameHeight)
val videoItem = SVGAVideoEntity(
MovieEntity.ADAPTER.decode(it),
File(cacheKey),
mFrameWidth,
mFrameHeight
)
videoItem.prepare {
LogUtils.info(TAG, "decode from input stream, inflate end")
this.invokeCompleteCallback(videoItem, callback)
}
} ?: LogUtils.error(TAG, "inflate(bytes) cause exception")

} ?: this.invokeErrorCallback(
Exception("inflate(bytes) cause exception"),
callback
)
}
} ?: LogUtils.error(TAG, "readAsBytes(inputStream) cause exception")
} ?: this.invokeErrorCallback(
Exception("readAsBytes(inputStream) cause exception"),
callback
)
} catch (e: java.lang.Exception) {
this.invokeErrorCallback(e, callback)
} finally {
Expand Down Expand Up @@ -251,10 +346,6 @@ class SVGAParser(context: Context?) {
}
}

private fun isCached(cacheKey: String): Boolean {
return buildCacheDir(cacheKey).exists()
}

private fun decodeFromCacheKey(cacheKey: String, callback: ParseCompletion?) {
LogUtils.info(TAG, "================ decode from cache ================")
LogUtils.debug(TAG, "decodeFromCacheKey called with cacheKey : $cacheKey")
Expand Down Expand Up @@ -311,30 +402,14 @@ class SVGAParser(context: Context?) {
}
}

private fun buildCacheKey(str: String): String {
val messageDigest = MessageDigest.getInstance("MD5")
messageDigest.update(str.toByteArray(charset("UTF-8")))
val digest = messageDigest.digest()
var sb = ""
for (b in digest) {
sb += String.format("%02x", b)
}
return sb
}

private fun buildCacheKey(url: URL): String = buildCacheKey(url.toString())

private fun buildCacheDir(cacheKey: String): File = File(mContext?.cacheDir?.absolutePath + "/" + cacheKey + "/")

private fun readAsBytes(inputStream: InputStream): ByteArray? {
ByteArrayOutputStream().use { byteArrayOutputStream ->
val byteArray = ByteArray(2048)
while (true) {
val count = inputStream.read(byteArray, 0, 2048)
if (count <= 0) {
break
}
else {
} else {
byteArrayOutputStream.write(byteArray, 0, count)
}
}
Expand Down Expand Up @@ -362,7 +437,7 @@ class SVGAParser(context: Context?) {

private fun unzip(inputStream: InputStream, cacheKey: String) {
LogUtils.info(TAG, "================ unzip prepare ================")
val cacheDir = this.buildCacheDir(cacheKey)
val cacheDir = SVGACache.buildCacheDir(cacheKey)
cacheDir.mkdirs()
try {
BufferedInputStream(inputStream).use {
Expand Down
Loading

0 comments on commit 84fcf55

Please sign in to comment.