Skip to content

Commit

Permalink
LottieCompositionFactory and Caching Cleanup (#959)
Browse files Browse the repository at this point in the history
This cleans up a number of things related to the LottieCompositionFactory.
It:

* Handled task caching at the LottieCompositionFactory level so it is guaranteed to work everywhere. The existing architecture left several instances where compositions would not be cached.
* Simplified the way tasks are handled in LottieAnimationView
* Fixes #958 in which different cache keys are used for rawRes animations
* Removed deprecated factory APIs
  • Loading branch information
gpeal authored Sep 24, 2018
1 parent c567cbb commit e76e445
Show file tree
Hide file tree
Showing 7 changed files with 53 additions and 169 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import java.util.*
class LottieFontViewGroup @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
private val compositionMap = HashMap<String, LottieComposition>()
private val views = ArrayList<View>()

private val cursorView: LottieAnimationView by lazy { LottieAnimationView(context) }
Expand Down Expand Up @@ -149,15 +148,8 @@ class LottieFontViewGroup @JvmOverloads constructor(
// break;
// }
val fileName = "Mobilo/$letter.json"
if (compositionMap.containsKey(fileName)) {
addComposition(compositionMap[fileName]!!)
} else {
LottieCompositionFactory.fromAsset(context, fileName)
.addListener {
compositionMap.put(fileName, it)
addComposition(it)
}
}
LottieCompositionFactory.fromAsset(context, fileName)
.addListener { addComposition(it) }

return true
}
Expand Down
123 changes: 18 additions & 105 deletions lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,12 @@
* <p>
* You may set the animation in one of two ways:
* 1) Attrs: {@link R.styleable#LottieAnimationView_lottie_fileName}
* 2) Programmatically: {@link #setAnimation(String)}, {@link #setComposition(LottieComposition)},
* or {@link #setAnimation(JsonReader)}.
* 2) Programmatically:
* {@link #setAnimation(String)}
* {@link #setAnimation(JsonReader, String)}
* {@link #setAnimationFromJson(String, String)}
* {@link #setAnimationFromUrl(String)}
* {@link #setComposition(LottieComposition)}
* <p>
* You can set a default cache strategy with {@link R.attr#lottie_cacheStrategy}.
* <p>
Expand All @@ -51,22 +55,8 @@
*/
@SuppressWarnings({"unused", "WeakerAccess"}) public class LottieAnimationView extends AppCompatImageView {

public static final CacheStrategy DEFAULT_CACHE_STRATEGY = CacheStrategy.Weak;

private static final String TAG = LottieAnimationView.class.getSimpleName();


/**
* Please migrate to LottieCompositionFactory. It has cleaner APIs and a LruCache built in.
* @see LottieCompositionFactory
*/
@Deprecated
public enum CacheStrategy {
None,
Weak,
Strong
}

private final LottieListener<LottieComposition> loadedListener = new LottieListener<LottieComposition>() {
@Override public void onResult(LottieComposition composition) {
setComposition(composition);
Expand All @@ -80,15 +70,14 @@ public enum CacheStrategy {
};

private final LottieDrawable lottieDrawable = new LottieDrawable();
private CacheStrategy defaultCacheStrategy;
private String animationName;
private @RawRes int animationResId;
private boolean wasAnimatingWhenDetached = false;
private boolean autoPlay = false;
private boolean useHardwareLayer = false;
private Set<LottieOnCompositionLoadedListener> lottieOnCompositionLoadedListeners = new HashSet<>();

@Nullable private LottieTask compositionTask;
@Nullable private LottieTask<LottieComposition> compositionTask;
/** Can be null because it is created async */
@Nullable private LottieComposition composition;

Expand All @@ -109,10 +98,6 @@ public LottieAnimationView(Context context, AttributeSet attrs, int defStyleAttr

private void init(@Nullable AttributeSet attrs) {
TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.LottieAnimationView);
int cacheStrategyOrdinal = ta.getInt(
R.styleable.LottieAnimationView_lottie_cacheStrategy,
DEFAULT_CACHE_STRATEGY.ordinal());
this.defaultCacheStrategy = CacheStrategy.values()[cacheStrategyOrdinal];
if (!isInEditMode()) {
boolean hasRawRes = ta.hasValue(R.styleable.LottieAnimationView_lottie_rawRes);
boolean hasFileName = ta.hasValue(R.styleable.LottieAnimationView_lottie_fileName);
Expand Down Expand Up @@ -268,9 +253,7 @@ private void setImageDrawable(Drawable drawable, boolean recycle) {
@VisibleForTesting void recycleBitmaps() {
// AppCompatImageView constructor will set the image when set from xml
// before LottieDrawable has been initialized
if (lottieDrawable != null) {
lottieDrawable.recycleBitmaps();
}
lottieDrawable.recycleBitmaps();
}

/**
Expand Down Expand Up @@ -339,82 +322,20 @@ public boolean getUseHardwareAcceleration() {
return useHardwareLayer;
}

/**
* cacheStrategy is deprecated. Compositions are now cached by default.
*
* @see #setAnimationRawRes(int)
*/
@Deprecated
public void setAnimation(@RawRes final int rawRes, final CacheStrategy cacheStrategy) {
setAnimation(rawRes);
}

/**
* Sets the animation from a file in the raw directory.
* This will load and deserialize the file asynchronously.
*/
public void setAnimation(@RawRes final int rawRes) {
this.animationResId = rawRes;
animationName = null;
LottieComposition cachedComposition = LottieCompositionCache.getInstance().getRawRes(rawRes);
if (cachedComposition != null) {
setComposition(cachedComposition);
return;
}

clearComposition();
cancelLoaderTask();
compositionTask = LottieCompositionFactory.fromRawRes(getContext(), rawRes)
.addListener(new LottieListener<LottieComposition>() {
@Override public void onResult(LottieComposition composition) {
LottieCompositionCache.getInstance().put(rawRes, composition);
}
})
.addListener(loadedListener)
.addFailureListener(failureListener);
}

/**
* cacheStrategy is deprecated. Compositions are now cached by default.
*
* @see #setAnimationAsset(String)
*/
@Deprecated
public void setAnimation(String assetName, CacheStrategy cacheStrategy) {
setAnimation(assetName);
setCompositionTask(LottieCompositionFactory.fromRawRes(getContext(), rawRes));
}

public void setAnimation(final String assetName) {
this.animationName = assetName;
animationResId = 0;
LottieComposition cachedComposition = LottieCompositionCache.getInstance().get(assetName);
if (cachedComposition != null) {
setComposition(cachedComposition);
return;
}

clearComposition();
cancelLoaderTask();
compositionTask = LottieCompositionFactory.fromAsset(getContext(), assetName)
.addListener(new LottieListener<LottieComposition>() {
@Override public void onResult(LottieComposition composition) {
LottieCompositionCache.getInstance().put(assetName, composition);
}
})
.addListener(loadedListener)
.addFailureListener(failureListener);
}

/**
* @see #setAnimation(JsonReader) which is more efficient than using a JSONObject.
* For animations loaded from the network, use {@link #setAnimationFromJson(String)}.
*
* If you must use a JSONObject, you can convert it to a StreamReader with:
* `new JsonReader(new StringReader(json.toString()));`
*/
@Deprecated
public void setAnimation(JSONObject json) {
setAnimation(new JsonReader(new StringReader(json.toString())));
setCompositionTask(LottieCompositionFactory.fromAsset(getContext(), assetName));
}

/**
Expand All @@ -434,14 +355,6 @@ public void setAnimationFromJson(String jsonString, @Nullable String cacheKey) {
setAnimation(new JsonReader(new StringReader(jsonString)), cacheKey);
}

/**
* @see #setAnimation(JsonReader, String)
*/
@Deprecated
public void setAnimation(JsonReader reader) {
setAnimation(reader, null);
}

/**
* Sets the animation from a JSONReader.
* This will load and deserialize the file asynchronously.
Expand All @@ -450,11 +363,7 @@ public void setAnimation(JsonReader reader) {
* bodymovin json from the network and pass it directly here.
*/
public void setAnimation(JsonReader reader, @Nullable String cacheKey) {
clearComposition();
cancelLoaderTask();
compositionTask = LottieCompositionFactory.fromJsonReader(reader, cacheKey)
.addListener(loadedListener)
.addFailureListener(failureListener);
setCompositionTask(LottieCompositionFactory.fromJsonReader(reader, cacheKey));
}

/**
Expand All @@ -466,11 +375,15 @@ public void setAnimation(JsonReader reader, @Nullable String cacheKey) {
* can be accessed immediately for subsequent requests. If the file does not parse to a composition, the temporary file will be deleted.
*/
public void setAnimationFromUrl(String url) {
setCompositionTask(LottieCompositionFactory.fromUrl(getContext(), url));
}

private void setCompositionTask(LottieTask<LottieComposition> compositionTask) {
clearComposition();
cancelLoaderTask();
compositionTask = LottieCompositionFactory.fromUrl(getContext(), url)
.addListener(loadedListener)
.addFailureListener(failureListener);
this.compositionTask = compositionTask
.addListener(loadedListener)
.addFailureListener(failureListener);
}

private void cancelLoaderTask() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import android.support.annotation.RawRes;
import android.support.annotation.WorkerThread;
import android.util.JsonReader;
import android.util.Log;

import com.airbnb.lottie.model.LottieCompositionCache;
import com.airbnb.lottie.network.NetworkFetcher;
Expand All @@ -34,6 +35,7 @@
* In-progress tasks will also be held so they can be returned for subsequent requests for the same
* animation prior to the cache being populated.
*/
@SuppressWarnings({"WeakerAccess", "unused"})
public class LottieCompositionFactory {
/**
* Keep a map of cache keys to in-progress tasks and return them for new requests.
Expand Down Expand Up @@ -110,7 +112,7 @@ public static LottieTask<LottieComposition> fromRawRes(Context context, @RawRes
// Prevent accidentally leaking an Activity.
final Context appContext = context.getApplicationContext();
return cache(rawResCacheKey(rawRes), new Callable<LottieResult<LottieComposition>>() {
@Override public LottieResult<LottieComposition> call() throws Exception {
@Override public LottieResult<LottieComposition> call() {
return fromRawResSync(appContext, rawRes);
}
});
Expand All @@ -137,11 +139,11 @@ private static String rawResCacheKey(@RawRes int resId) {
/**
* Auto-closes the stream.
*
* @see #fromJsonInputStreamSync(InputStream, boolean)
* @see #fromJsonInputStreamSync(InputStream, String, boolean)
*/
public static LottieTask<LottieComposition> fromJsonInputStream(final InputStream stream, @Nullable final String cacheKey) {
return cache(cacheKey, new Callable<LottieResult<LottieComposition>>() {
@Override public LottieResult<LottieComposition> call() throws Exception {
@Override public LottieResult<LottieComposition> call() {
return fromJsonInputStreamSync(stream, cacheKey);
}
});
Expand Down Expand Up @@ -172,7 +174,8 @@ private static LottieResult<LottieComposition> fromJsonInputStreamSync(InputStre
@Deprecated
public static LottieTask<LottieComposition> fromJson(final JSONObject json, @Nullable final String cacheKey) {
return cache(cacheKey, new Callable<LottieResult<LottieComposition>>() {
@Override public LottieResult<LottieComposition> call() throws Exception {
@Override public LottieResult<LottieComposition> call() {
//noinspection deprecation
return fromJsonSync(json, cacheKey);
}
});
Expand All @@ -190,11 +193,11 @@ public static LottieResult<LottieComposition> fromJsonSync(JSONObject json, @Nul
}

/**
* @see #fromJsonStringSync(String)
* @see #fromJsonStringSync(String, String)
*/
public static LottieTask<LottieComposition> fromJsonString(final String json, @Nullable final String cacheKey) {
return cache(cacheKey, new Callable<LottieResult<LottieComposition>>() {
@Override public LottieResult<LottieComposition> call() throws Exception {
@Override public LottieResult<LottieComposition> call() {
return fromJsonStringSync(json, cacheKey);
}
});
Expand All @@ -211,7 +214,7 @@ public static LottieResult<LottieComposition> fromJsonStringSync(String json, @N

public static LottieTask<LottieComposition> fromJsonReader(final JsonReader reader, @Nullable final String cacheKey) {
return cache(cacheKey, new Callable<LottieResult<LottieComposition>>() {
@Override public LottieResult<LottieComposition> call() throws Exception {
@Override public LottieResult<LottieComposition> call() {
return fromJsonReaderSync(reader, cacheKey);
}
});
Expand All @@ -233,7 +236,7 @@ public static LottieResult<LottieComposition> fromJsonReaderSync(JsonReader read

public static LottieTask<LottieComposition> fromZipStream(final ZipInputStream inputStream, @Nullable final String cacheKey) {
return cache(cacheKey, new Callable<LottieResult<LottieComposition>>() {
@Override public LottieResult<LottieComposition> call() throws Exception {
@Override public LottieResult<LottieComposition> call() {
return fromZipStreamSync(inputStream, cacheKey);
}
});
Expand Down Expand Up @@ -317,14 +320,28 @@ private static LottieImageAsset findImageAssetForFileName(LottieComposition comp
* If not, create a new task for the callable.
* Then, add the new task to the task cache and set up listeners to it gets cleared when done.
*/
private static LottieTask<LottieComposition> cache(final String cacheKey, Callable<LottieResult<LottieComposition>> callable) {
private static LottieTask<LottieComposition> cache(
@Nullable final String cacheKey, Callable<LottieResult<LottieComposition>> callable) {
final LottieComposition cachedComposition = LottieCompositionCache.getInstance().get(cacheKey);
if (cachedComposition != null) {
return new LottieTask<>(new Callable<LottieResult<LottieComposition>>() {
@Override
public LottieResult<LottieComposition> call() {
Log.d("Gabe", "call\treturning from cache");
return new LottieResult<>(cachedComposition);
}
});
}
if (taskCache.containsKey(cacheKey)) {
return taskCache.get(cacheKey);
}

LottieTask<LottieComposition> task = new LottieTask<>(callable);
task.addListener(new LottieListener<LottieComposition>() {
@Override public void onResult(LottieComposition result) {
if (cacheKey != null) {
LottieCompositionCache.getInstance().put(cacheKey, result);
}
taskCache.remove(cacheKey);
}
});
Expand Down
2 changes: 1 addition & 1 deletion lottie/src/main/java/com/airbnb/lottie/LottieTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ public synchronized LottieTask<T> addFailureListener(LottieListener<Throwable> l
* a listener if neccesary.
* @return the task for call chaining.
*/
public synchronized LottieTask<T> removeFailureListener(LottieListener<T> listener) {
public synchronized LottieTask<T> removeFailureListener(LottieListener<Throwable> listener) {
failureListeners.remove(listener);
stopTaskObserverIfNeeded();
return this;
Expand Down
Loading

0 comments on commit e76e445

Please sign in to comment.