1 package com.airbnb.lottie; 2 3 import android.os.Handler; 4 import android.os.Looper; 5 import androidx.annotation.Nullable; 6 import androidx.annotation.RestrictTo; 7 8 import com.airbnb.lottie.utils.Logger; 9 10 import java.util.ArrayList; 11 import java.util.LinkedHashSet; 12 import java.util.List; 13 import java.util.Set; 14 import java.util.concurrent.Callable; 15 import java.util.concurrent.ExecutionException; 16 import java.util.concurrent.Executor; 17 import java.util.concurrent.Executors; 18 import java.util.concurrent.FutureTask; 19 20 /** 21 * Helper to run asynchronous tasks with a result. 22 * Results can be obtained with {@link #addListener(LottieListener)}. 23 * Failures can be obtained with {@link #addFailureListener(LottieListener)}. 24 * 25 * A task will produce a single result or a single failure. 26 */ 27 public class LottieTask<T> { 28 29 /** 30 * Set this to change the executor that LottieTasks are run on. This will be the executor that composition parsing and url 31 * fetching happens on. 32 * 33 * You may change this to run deserialization synchronously for testing. 34 */ 35 @SuppressWarnings("WeakerAccess") 36 public static Executor EXECUTOR = Executors.newCachedThreadPool(); 37 38 /* Preserve add order. */ 39 private final Set<LottieListener<T>> successListeners = new LinkedHashSet<>(1); 40 private final Set<LottieListener<Throwable>> failureListeners = new LinkedHashSet<>(1); 41 private final Handler handler = new Handler(Looper.getMainLooper()); 42 43 @Nullable private volatile LottieResult<T> result = null; 44 45 @RestrictTo(RestrictTo.Scope.LIBRARY) LottieTask(Callable<LottieResult<T>> runnable)46 public LottieTask(Callable<LottieResult<T>> runnable) { 47 this(runnable, false); 48 } 49 50 /** 51 * runNow is only used for testing. 52 */ 53 @RestrictTo(RestrictTo.Scope.LIBRARY) LottieTask(Callable<LottieResult<T>> runnable, boolean runNow)54 LottieTask(Callable<LottieResult<T>> runnable, boolean runNow) { 55 if (runNow) { 56 try { 57 setResult(runnable.call()); 58 } catch (Throwable e) { 59 setResult(new LottieResult<T>(e)); 60 } 61 } else { 62 EXECUTOR.execute(new LottieFutureTask(runnable)); 63 } 64 } 65 setResult(@ullable LottieResult<T> result)66 private void setResult(@Nullable LottieResult<T> result) { 67 if (this.result != null) { 68 throw new IllegalStateException("A task may only be set once."); 69 } 70 this.result = result; 71 notifyListeners(); 72 } 73 74 /** 75 * Add a task listener. If the task has completed, the listener will be called synchronously. 76 * @return the task for call chaining. 77 */ addListener(LottieListener<T> listener)78 public synchronized LottieTask<T> addListener(LottieListener<T> listener) { 79 if (result != null && result.getValue() != null) { 80 listener.onResult(result.getValue()); 81 } 82 83 successListeners.add(listener); 84 return this; 85 } 86 87 /** 88 * Remove a given task listener. The task will continue to execute so you can re-add 89 * a listener if neccesary. 90 * @return the task for call chaining. 91 */ removeListener(LottieListener<T> listener)92 public synchronized LottieTask<T> removeListener(LottieListener<T> listener) { 93 successListeners.remove(listener); 94 return this; 95 } 96 97 /** 98 * Add a task failure listener. This will only be called in the even that an exception 99 * occurs. If an exception has already occurred, the listener will be called immediately. 100 * @return the task for call chaining. 101 */ addFailureListener(LottieListener<Throwable> listener)102 public synchronized LottieTask<T> addFailureListener(LottieListener<Throwable> listener) { 103 if (result != null && result.getException() != null) { 104 listener.onResult(result.getException()); 105 } 106 107 failureListeners.add(listener); 108 return this; 109 } 110 111 /** 112 * Remove a given task failure listener. The task will continue to execute so you can re-add 113 * a listener if neccesary. 114 * @return the task for call chaining. 115 */ removeFailureListener(LottieListener<Throwable> listener)116 public synchronized LottieTask<T> removeFailureListener(LottieListener<Throwable> listener) { 117 failureListeners.remove(listener); 118 return this; 119 } 120 notifyListeners()121 private void notifyListeners() { 122 // Listeners should be called on the main thread. 123 handler.post(new Runnable() { 124 @Override public void run() { 125 if (result == null) { 126 return; 127 } 128 // Local reference in case it gets set on a background thread. 129 LottieResult<T> result = LottieTask.this.result; 130 if (result.getValue() != null) { 131 notifySuccessListeners(result.getValue()); 132 } else { 133 notifyFailureListeners(result.getException()); 134 } 135 } 136 }); 137 } 138 notifySuccessListeners(T value)139 private synchronized void notifySuccessListeners(T value) { 140 // Allows listeners to remove themselves in onResult. 141 // Otherwise we risk ConcurrentModificationException. 142 List<LottieListener<T>> listenersCopy = new ArrayList<>(successListeners); 143 for (LottieListener<T> l : listenersCopy) { 144 l.onResult(value); 145 } 146 } 147 notifyFailureListeners(Throwable e)148 private synchronized void notifyFailureListeners(Throwable e) { 149 // Allows listeners to remove themselves in onResult. 150 // Otherwise we risk ConcurrentModificationException. 151 List<LottieListener<Throwable>> listenersCopy = new ArrayList<>(failureListeners); 152 if (listenersCopy.isEmpty()) { 153 Logger.warning("Lottie encountered an error but no failure listener was added:", e); 154 return; 155 } 156 157 for (LottieListener<Throwable> l : listenersCopy) { 158 l.onResult(e); 159 } 160 } 161 162 private class LottieFutureTask extends FutureTask<LottieResult<T>> { LottieFutureTask(Callable<LottieResult<T>> callable)163 LottieFutureTask(Callable<LottieResult<T>> callable) { 164 super(callable); 165 } 166 167 @Override done()168 protected void done() { 169 if (isCancelled()) { 170 // We don't need to notify and listeners if the task is cancelled. 171 return; 172 } 173 174 try { 175 setResult(get()); 176 } catch (InterruptedException | ExecutionException e) { 177 setResult(new LottieResult<T>(e)); 178 } 179 } 180 } 181 } 182