diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java b/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java index 0589432364..1ff727ba7c 100644 --- a/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java +++ b/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java @@ -29,6 +29,7 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -92,15 +93,12 @@ public static LottieTask fromUrl(final Context context, final * might need an animation in the future. */ public static LottieTask fromUrl(final Context context, final String url, @Nullable final String cacheKey) { - return cache(cacheKey, new Callable>() { - @Override - public LottieResult call() { - LottieResult result = L.networkFetcher(context).fetchSync(url, cacheKey); - if (cacheKey != null && result.getValue() != null) { - LottieCompositionCache.getInstance().put(cacheKey, result.getValue()); - } - return result; + return cache(cacheKey, () -> { + LottieResult result = L.networkFetcher(context).fetchSync(url, cacheKey); + if (cacheKey != null && result.getValue() != null) { + LottieCompositionCache.getInstance().put(cacheKey, result.getValue()); } + return result; }); } @@ -155,12 +153,7 @@ public static LottieTask fromAsset(Context context, final Str public static LottieTask fromAsset(Context context, final String fileName, @Nullable final String cacheKey) { // Prevent accidentally leaking an Activity. final Context appContext = context.getApplicationContext(); - return cache(cacheKey, new Callable>() { - @Override - public LottieResult call() { - return fromAssetSync(appContext, fileName, cacheKey); - } - }); + return cache(cacheKey, () -> fromAssetSync(appContext, fileName, cacheKey)); } /** @@ -226,13 +219,10 @@ public static LottieTask fromRawRes(Context context, @RawRes // Prevent accidentally leaking an Activity. final WeakReference contextRef = new WeakReference<>(context); final Context appContext = context.getApplicationContext(); - return cache(cacheKey, new Callable>() { - @Override - public LottieResult call() { - @Nullable Context originalContext = contextRef.get(); - Context context = originalContext != null ? originalContext : appContext; - return fromRawResSync(context, rawRes, cacheKey); - } + return cache(cacheKey, () -> { + @Nullable Context originalContext = contextRef.get(); + Context context1 = originalContext != null ? originalContext : appContext; + return fromRawResSync(context1, rawRes, cacheKey); }); } @@ -290,12 +280,7 @@ private static boolean isNightMode(Context context) { * @see #fromJsonInputStreamSync(InputStream, String, boolean) */ public static LottieTask fromJsonInputStream(final InputStream stream, @Nullable final String cacheKey) { - return cache(cacheKey, new Callable>() { - @Override - public LottieResult call() { - return fromJsonInputStreamSync(stream, cacheKey); - } - }); + return cache(cacheKey, () -> fromJsonInputStreamSync(stream, cacheKey)); } /** @@ -324,12 +309,9 @@ private static LottieResult fromJsonInputStreamSync(InputStre */ @Deprecated public static LottieTask fromJson(final JSONObject json, @Nullable final String cacheKey) { - return cache(cacheKey, new Callable>() { - @Override - public LottieResult call() { - //noinspection deprecation - return fromJsonSync(json, cacheKey); - } + return cache(cacheKey, () -> { + //noinspection deprecation + return fromJsonSync(json, cacheKey); }); } @@ -348,12 +330,7 @@ public static LottieResult fromJsonSync(JSONObject json, @Nul * @see #fromJsonStringSync(String, String) */ public static LottieTask fromJsonString(final String json, @Nullable final String cacheKey) { - return cache(cacheKey, new Callable>() { - @Override - public LottieResult call() { - return fromJsonStringSync(json, cacheKey); - } - }); + return cache(cacheKey, () -> fromJsonStringSync(json, cacheKey)); } /** @@ -369,12 +346,7 @@ public static LottieResult fromJsonStringSync(String json, @N } public static LottieTask fromJsonReader(final JsonReader reader, @Nullable final String cacheKey) { - return cache(cacheKey, new Callable>() { - @Override - public LottieResult call() { - return fromJsonReaderSync(reader, cacheKey); - } - }); + return cache(cacheKey, () -> fromJsonReaderSync(reader, cacheKey)); } @@ -403,12 +375,7 @@ private static LottieResult fromJsonReaderSyncInternal( public static LottieTask fromZipStream(final ZipInputStream inputStream, @Nullable final String cacheKey) { - return cache(cacheKey, new Callable>() { - @Override - public LottieResult call() { - return fromZipStreamSync(inputStream, cacheKey); - } - }); + return cache(cacheKey, () -> fromZipStreamSync(inputStream, cacheKey)); } /** @@ -521,12 +488,7 @@ private static LottieTask cache( @Nullable final String cacheKey, Callable> callable) { final LottieComposition cachedComposition = cacheKey == null ? null : LottieCompositionCache.getInstance().get(cacheKey); if (cachedComposition != null) { - return new LottieTask<>(new Callable>() { - @Override - public LottieResult call() { - return new LottieResult<>(cachedComposition); - } - }); + return new LottieTask<>(() -> new LottieResult<>(cachedComposition)); } if (cacheKey != null && taskCache.containsKey(cacheKey)) { return taskCache.get(cacheKey); @@ -534,19 +496,22 @@ public LottieResult call() { LottieTask task = new LottieTask<>(callable); if (cacheKey != null) { - task.addListener(new LottieListener() { - @Override - public void onResult(LottieComposition result) { - taskCache.remove(cacheKey); - } + AtomicBoolean resultAlreadyCalled = new AtomicBoolean(false); + task.addListener(result -> { + taskCache.remove(cacheKey); + resultAlreadyCalled.set(true); }); - task.addFailureListener(new LottieListener() { - @Override - public void onResult(Throwable result) { - taskCache.remove(cacheKey); - } + task.addFailureListener(result -> { + taskCache.remove(cacheKey); + resultAlreadyCalled.set(true); }); - taskCache.put(cacheKey, task); + // It is technically possible for the task to finish and for the listeners to get called + // before this code runs. If this happens, the task will be put in taskCache but never removed. + // This would require this thread to be sleeping at exactly this point in the code + // for long enough for the task to finish and call the listeners. Unlikely but not impossible. + if (!resultAlreadyCalled.get()) { + taskCache.put(cacheKey, task); + } } return task; } diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieTask.java b/lottie/src/main/java/com/airbnb/lottie/LottieTask.java index 2b9e914df5..6d1ae568e0 100644 --- a/lottie/src/main/java/com/airbnb/lottie/LottieTask.java +++ b/lottie/src/main/java/com/airbnb/lottie/LottieTask.java @@ -25,7 +25,7 @@ *

* A task will produce a single result or a single failure. */ -public class LottieTask { +@SuppressWarnings("UnusedReturnValue") public class LottieTask { /** * Set this to change the executor that LottieTasks are run on. This will be the executor that composition parsing and url @@ -56,7 +56,7 @@ public LottieTask(Callable> runnable) { try { setResult(runnable.call()); } catch (Throwable e) { - setResult(new LottieResult(e)); + setResult(new LottieResult<>(e)); } } else { EXECUTOR.execute(new LottieFutureTask(runnable)); @@ -77,6 +77,7 @@ private void setResult(@Nullable LottieResult result) { * @return the task for call chaining. */ public synchronized LottieTask addListener(LottieListener listener) { + LottieResult result = this.result; if (result != null && result.getValue() != null) { listener.onResult(result.getValue()); } @@ -87,7 +88,7 @@ public synchronized LottieTask addListener(LottieListener listener) { /** * Remove a given task listener. The task will continue to execute so you can re-add - * a listener if neccesary. + * a listener if necessary. * * @return the task for call chaining. */ @@ -103,6 +104,7 @@ public synchronized LottieTask removeListener(LottieListener listener) { * @return the task for call chaining. */ public synchronized LottieTask addFailureListener(LottieListener listener) { + LottieResult result = this.result; if (result != null && result.getException() != null) { listener.onResult(result.getException()); } @@ -113,7 +115,7 @@ public synchronized LottieTask addFailureListener(LottieListener l /** * Remove a given task failure listener. The task will continue to execute so you can re-add - * a listener if neccesary. + * a listener if necessary. * * @return the task for call chaining. */ @@ -124,18 +126,16 @@ public synchronized LottieTask removeFailureListener(LottieListener result = LottieTask.this.result; - if (result.getValue() != null) { - notifySuccessListeners(result.getValue()); - } else { - notifyFailureListeners(result.getException()); - } + handler.post(() -> { + // Local reference in case it gets set on a background thread. + LottieResult result = LottieTask.this.result; + if (result == null) { + return; + } + if (result.getValue() != null) { + notifySuccessListeners(result.getValue()); + } else { + notifyFailureListeners(result.getException()); } }); } @@ -178,7 +178,7 @@ protected void done() { try { setResult(get()); } catch (InterruptedException | ExecutionException e) { - setResult(new LottieResult(e)); + setResult(new LottieResult<>(e)); } } }