Add the ability to set custom cache keys or set non cache key
diff --git a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/PlayerViewModel.kt b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/PlayerViewModel.kt
index dac6429..46e414e 100644
--- a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/PlayerViewModel.kt
+++ b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/PlayerViewModel.kt
@@ -40,9 +40,9 @@
         val url = args.url ?: args.animationData?.lottieLink
 
         when {
-            url != null -> LottieCompositionFactory.fromUrl(application, url)
+            url != null -> LottieCompositionFactory.fromUrl(application, url, null)
             args.fileUri != null -> taskForUri(args.fileUri)
-            args.asset != null -> LottieCompositionFactory.fromAsset(application, args.asset)
+            args.asset != null -> LottieCompositionFactory.fromAsset(application, args.asset, null)
             else -> throw IllegalArgumentException("Don't know how to fetch animation for $args")
         }
                 .addListener {
@@ -64,7 +64,7 @@
             return LottieTask { throw e }
         }
 
-        return LottieCompositionFactory.fromJsonInputStream(fis, uri.toString())
+        return LottieCompositionFactory.fromJsonInputStream(fis, null)
     }
 
     fun toggleRenderGraphVisible() = setState { copy(renderGraphVisible = !renderGraphVisible) }
diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java b/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java
index 44b58bb..3aff8e3 100644
--- a/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java
+++ b/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java
@@ -100,6 +100,7 @@
   private boolean wasAnimatingWhenNotShown = false;
   private boolean wasAnimatingWhenDetached = false;
   private boolean autoPlay = false;
+  private boolean cacheComposition = true;
   private RenderMode renderMode = RenderMode.AUTOMATIC;
   private Set<LottieOnCompositionLoadedListener> lottieOnCompositionLoadedListeners = new HashSet<>();
   /**
@@ -133,6 +134,7 @@
   private void init(@Nullable AttributeSet attrs) {
     TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.LottieAnimationView);
     if (!isInEditMode()) {
+      cacheComposition = ta.getBoolean(R.styleable.LottieAnimationView_lottie_cacheComposition, true);
       boolean hasRawRes = ta.hasValue(R.styleable.LottieAnimationView_lottie_rawRes);
       boolean hasFileName = ta.hasValue(R.styleable.LottieAnimationView_lottie_fileName);
       boolean hasUrl = ta.hasValue(R.styleable.LottieAnimationView_lottie_url);
@@ -348,19 +350,35 @@
   }
 
   /**
+   * If set to true, all future compositions that are set will be cached so that they don't need to be parsed
+   * next time they are loaded. This won't apply to compositions that have already been loaded.
+   *
+   * Defaults to true.
+   *
+   * {@link R.attr#lottie_cacheComposition}
+   */
+  public void  setCacheComposition(boolean cacheComposition) {
+    this.cacheComposition = cacheComposition;
+  }
+
+  /**
    * 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;
-    setCompositionTask(LottieCompositionFactory.fromRawRes(getContext(), rawRes));
+    LottieTask<LottieComposition> task = cacheComposition ?
+        LottieCompositionFactory.fromRawRes(getContext(), rawRes) : LottieCompositionFactory.fromRawRes(getContext(), rawRes, null);
+    setCompositionTask(task);
   }
 
   public void setAnimation(final String assetName) {
     this.animationName = assetName;
     animationResId = 0;
-    setCompositionTask(LottieCompositionFactory.fromAsset(getContext(), assetName));
+    LottieTask<LottieComposition> task = cacheComposition ?
+        LottieCompositionFactory.fromAsset(getContext(), assetName) : LottieCompositionFactory.fromAsset(getContext(), assetName, null);
+    setCompositionTask(task);
   }
 
   /**
@@ -400,7 +418,9 @@
    * 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));
+    LottieTask<LottieComposition> task = cacheComposition ?
+        LottieCompositionFactory.fromUrl(getContext(), url) : LottieCompositionFactory.fromUrl(getContext(), url, null);
+    setCompositionTask(task);
   }
 
   /**
diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java b/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java
index f297279..a1d394f 100644
--- a/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java
+++ b/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java
@@ -65,10 +65,20 @@
    * Fetch an animation from an http url. Once it is downloaded once, Lottie will cache the file to disk for
    * future use. Because of this, you may call `fromUrl` ahead of time to warm the cache if you think you
    * might need an animation in the future.
+   *
+   * To skip the cache, add null as a third parameter.
    */
   public static LottieTask<LottieComposition> fromUrl(final Context context, final String url) {
-    String urlCacheKey = "url_" + url;
-    return cache(urlCacheKey, new Callable<LottieResult<LottieComposition>>() {
+    return fromUrl(context, url, "url_" + url);
+  }
+
+  /**
+   * Fetch an animation from an http url. Once it is downloaded once, Lottie will cache the file to disk for
+   * future use. Because of this, you may call `fromUrl` ahead of time to warm the cache if you think you
+   * might need an animation in the future.
+   */
+  public static LottieTask<LottieComposition> fromUrl(final Context context, final String url, @Nullable String cacheKey) {
+    return cache(cacheKey, new Callable<LottieResult<LottieComposition>>() {
       @Override
       public LottieResult<LottieComposition> call() {
         return NetworkFetcher.fetchSync(context, url);
@@ -91,15 +101,31 @@
    * The asset file name will be used as a cache key so future usages won't have to parse the json again.
    * However, if your animation has images, you may package the json and images as a single flattened zip file in assets.
    *
+   * To skip the cache, add null as a third parameter.
+   *
    * @see #fromZipStream(ZipInputStream, String)
    */
   public static LottieTask<LottieComposition> fromAsset(Context context, final String fileName) {
+    String cacheKey = "asset_" + fileName;
+    return fromAsset(context, fileName, cacheKey);
+  }
+
+  /**
+   * Parse an animation from src/main/assets. It is recommended to use {@link #fromRawRes(Context, int)} instead.
+   * The asset file name will be used as a cache key so future usages won't have to parse the json again.
+   * However, if your animation has images, you may package the json and images as a single flattened zip file in assets.
+   *
+   * Pass null as the cache key to skip the cache.
+   *
+   * @see #fromZipStream(ZipInputStream, String)
+   */
+  public static LottieTask<LottieComposition> fromAsset(Context context, final String fileName, @Nullable final String cacheKey) {
     // Prevent accidentally leaking an Activity.
     final Context appContext = context.getApplicationContext();
-    return cache(fileName, new Callable<LottieResult<LottieComposition>>() {
+    return cache(cacheKey, new Callable<LottieResult<LottieComposition>>() {
       @Override
       public LottieResult<LottieComposition> call() {
-        return fromAssetSync(appContext, fileName);
+        return fromAssetSync(appContext, fileName, cacheKey);
       }
     });
   }
@@ -109,12 +135,28 @@
    * The asset file name will be used as a cache key so future usages won't have to parse the json again.
    * However, if your animation has images, you may package the json and images as a single flattened zip file in assets.
    *
+   * To skip the cache, add null as a third parameter.
+   *
    * @see #fromZipStreamSync(ZipInputStream, String)
    */
   @WorkerThread
   public static LottieResult<LottieComposition> fromAssetSync(Context context, String fileName) {
-    try {
       String cacheKey = "asset_" + fileName;
+      return fromAssetSync(context, fileName, cacheKey);
+  }
+
+  /**
+   * Parse an animation from src/main/assets. It is recommended to use {@link #fromRawRes(Context, int)} instead.
+   * The asset file name will be used as a cache key so future usages won't have to parse the json again.
+   * However, if your animation has images, you may package the json and images as a single flattened zip file in assets.
+   *
+   * Pass null as the cache key to skip the cache.
+   *
+   * @see #fromZipStreamSync(ZipInputStream, String)
+   */
+  @WorkerThread
+  public static LottieResult<LottieComposition> fromAssetSync(Context context, String fileName, @Nullable String cacheKey) {
+    try {
       if (fileName.endsWith(".zip")) {
         return fromZipStreamSync(new ZipInputStream(context.getAssets().open(fileName)), cacheKey);
       }
@@ -131,12 +173,27 @@
    * The resource id will be used as a cache key so future usages won't parse the json again.
    * Note: to correctly load dark mode (-night) resources, make sure you pass Activity as a context (instead of e.g. the application context).
    * The Activity won't be leaked.
+   *
+   * To skip the cache, add null as a third parameter.
    */
   public static LottieTask<LottieComposition> fromRawRes(Context context, @RawRes final int rawRes) {
+    return fromRawRes(context, rawRes, rawResCacheKey(context, rawRes));
+  }
+
+  /**
+   * Parse an animation from raw/res. This is recommended over putting your animation in assets because
+   * it uses a hard reference to R.
+   * The resource id will be used as a cache key so future usages won't parse the json again.
+   * Note: to correctly load dark mode (-night) resources, make sure you pass Activity as a context (instead of e.g. the application context).
+   * The Activity won't be leaked.
+   *
+   * Pass null as the cache key to skip caching.
+   */
+  public static LottieTask<LottieComposition> fromRawRes(Context context, @RawRes final int rawRes, @Nullable String cacheKey) {
     // Prevent accidentally leaking an Activity.
     final WeakReference<Context> contextRef = new WeakReference<>(context);
     final Context appContext = context.getApplicationContext();
-    return cache(rawResCacheKey(context, rawRes), new Callable<LottieResult<LottieComposition>>() {
+    return cache(cacheKey, new Callable<LottieResult<LottieComposition>>() {
       @Override
       public LottieResult<LottieComposition> call() {
         @Nullable Context originalContext = contextRef.get();
@@ -152,11 +209,27 @@
    * The resource id will be used as a cache key so future usages won't parse the json again.
    * Note: to correctly load dark mode (-night) resources, make sure you pass Activity as a context (instead of e.g. the application context).
    * The Activity won't be leaked.
+   *
+   * To skip the cache, add null as a third parameter.
    */
   @WorkerThread
   public static LottieResult<LottieComposition> fromRawResSync(Context context, @RawRes int rawRes) {
+    return fromRawResSync(context, rawRes, rawResCacheKey(context, rawRes));
+  }
+
+  /**
+   * Parse an animation from raw/res. This is recommended over putting your animation in assets because
+   * it uses a hard reference to R.
+   * The resource id will be used as a cache key so future usages won't parse the json again.
+   * Note: to correctly load dark mode (-night) resources, make sure you pass Activity as a context (instead of e.g. the application context).
+   * The Activity won't be leaked.
+   *
+   * Pass null as the cache key to skip caching.
+   */
+  @WorkerThread
+  public static LottieResult<LottieComposition> fromRawResSync(Context context, @RawRes int rawRes, @Nullable String cacheKey) {
     try {
-      return fromJsonInputStreamSync(context.getResources().openRawResource(rawRes), rawResCacheKey(context, rawRes));
+      return fromJsonInputStreamSync(context.getResources().openRawResource(rawRes), cacheKey);
     } catch (Resources.NotFoundException e) {
       return new LottieResult<>(e);
     }
@@ -399,19 +472,21 @@
     }
 
     LottieTask<LottieComposition> task = new LottieTask<>(callable);
-    task.addListener(new LottieListener<LottieComposition>() {
-      @Override
-      public void onResult(LottieComposition result) {
-        taskCache.remove(cacheKey);
-      }
-    });
-    task.addFailureListener(new LottieListener<Throwable>() {
-      @Override
-      public void onResult(Throwable result) {
-        taskCache.remove(cacheKey);
-      }
-    });
-    taskCache.put(cacheKey, task);
+    if (cacheKey != null) {
+      task.addListener(new LottieListener<LottieComposition>() {
+        @Override
+        public void onResult(LottieComposition result) {
+          taskCache.remove(cacheKey);
+        }
+      });
+      task.addFailureListener(new LottieListener<Throwable>() {
+        @Override
+        public void onResult(Throwable result) {
+          taskCache.remove(cacheKey);
+        }
+      });
+      taskCache.put(cacheKey, task);
+    }
     return task;
   }
 }
diff --git a/lottie/src/main/res/values/attrs.xml b/lottie/src/main/res/values/attrs.xml
index 5d51c4a..41f4c7a 100644
--- a/lottie/src/main/res/values/attrs.xml
+++ b/lottie/src/main/res/values/attrs.xml
@@ -18,6 +18,7 @@
         <attr name="lottie_colorFilter" format="color" />
         <attr name="lottie_scale" format="float" />
         <attr name="lottie_speed" format="float" />
+        <attr name="lottie_cacheComposition" format="boolean" />
         <!-- These values must be kept in sync with the RenderMode enum -->
         <attr name="lottie_renderMode" format="enum">
             <enum name="automatic" value="0" />