Merge remote-tracking branch 'origin/master' into gpeal/network-cache-key
diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java b/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java
index ed780c1..0102049 100644
--- a/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java
+++ b/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java
@@ -425,6 +425,19 @@
}
/**
+ * Load a lottie animation from a url. The url can be a json file or a zip file. Use a zip file if you have images. Simply zip them together and lottie
+ * will unzip and link the images automatically.
+ *
+ * Under the hood, Lottie uses Java HttpURLConnection because it doesn't require any transitive networking dependencies. It will download the file
+ * to the application cache under a temporary name. If the file successfully parses to a composition, it will rename the temporary file to one that
+ * 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, @Nullable String cacheKey) {
+ LottieTask<LottieComposition> task = LottieCompositionFactory.fromUrl(getContext(), url, cacheKey);
+ setCompositionTask(task);
+ }
+
+ /**
* Set a default failure listener that will be called if any of the setAnimation APIs fail for any reason.
* This can be used to replace the default behavior.
*
diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java b/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java
index a1d394f..0ae8125 100644
--- a/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java
+++ b/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java
@@ -8,6 +8,7 @@
import android.os.Build;
import com.airbnb.lottie.model.LottieCompositionCache;
+import com.airbnb.lottie.network.NetworkCache;
import com.airbnb.lottie.network.NetworkFetcher;
import com.airbnb.lottie.parser.LottieCompositionMoshiParser;
import com.airbnb.lottie.parser.moshi.JsonReader;
@@ -61,6 +62,12 @@
LottieCompositionCache.getInstance().resize(size);
}
+ public static void clearCache(Context context) {
+ taskCache.clear();
+ LottieCompositionCache.getInstance().clear();
+ new NetworkCache(context).clear();
+ }
+
/**
* 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
@@ -77,11 +84,11 @@
* 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) {
+ public static LottieTask<LottieComposition> fromUrl(final Context context, final String url, @Nullable final String cacheKey) {
return cache(cacheKey, new Callable<LottieResult<LottieComposition>>() {
@Override
public LottieResult<LottieComposition> call() {
- return NetworkFetcher.fetchSync(context, url);
+ return NetworkFetcher.fetchSync(context, url, cacheKey);
}
});
}
@@ -93,7 +100,18 @@
*/
@WorkerThread
public static LottieResult<LottieComposition> fromUrlSync(Context context, String url) {
- return NetworkFetcher.fetchSync(context, url);
+ return fromUrlSync(context, 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.
+ */
+ @WorkerThread
+ public static LottieResult<LottieComposition> fromUrlSync(Context context, String url, @Nullable String cacheKey) {
+ return NetworkFetcher.fetchSync(context, url, cacheKey);
}
/**
diff --git a/lottie/src/main/java/com/airbnb/lottie/network/NetworkCache.java b/lottie/src/main/java/com/airbnb/lottie/network/NetworkCache.java
index dc8e5bd..71efef2 100644
--- a/lottie/src/main/java/com/airbnb/lottie/network/NetworkCache.java
+++ b/lottie/src/main/java/com/airbnb/lottie/network/NetworkCache.java
@@ -18,13 +18,24 @@
/**
* Helper class to save and restore animations fetched from an URL to the app disk cache.
*/
-class NetworkCache {
+public class NetworkCache {
private final Context appContext;
- private final String url;
- NetworkCache(Context appContext, String url) {
+ public NetworkCache(Context appContext) {
this.appContext = appContext.getApplicationContext();
- this.url = url;
+ }
+
+ public void clear() {
+ File parentDir = parentDir();
+ if (parentDir.exists()) {
+ File[] files = parentDir.listFiles();
+ if (files != null && files.length > 0) {
+ for (File file : parentDir.listFiles()) {
+ file.delete();
+ }
+ }
+ parentDir.delete();
+ }
}
/**
@@ -36,8 +47,8 @@
*/
@Nullable
@WorkerThread
- Pair<FileExtension, InputStream> fetch() {
- File cachedFile = null;
+ Pair<FileExtension, InputStream> fetch(String url) {
+ File cachedFile;
try {
cachedFile = getCachedFile(url);
} catch (FileNotFoundException e) {
@@ -70,9 +81,9 @@
* to an composition, {@link #renameTempFile(FileExtension)} should be called to move the file
* to its final location for future cache hits.
*/
- File writeTempCacheFile(InputStream stream, FileExtension extension) throws IOException {
+ File writeTempCacheFile(String url, InputStream stream, FileExtension extension) throws IOException {
String fileName = filenameForUrl(url, extension, true);
- File file = new File(appContext.getCacheDir(), fileName);
+ File file = new File(parentDir(), fileName);
try {
OutputStream output = new FileOutputStream(file);
//noinspection TryFinallyCanBeTryWithResources
@@ -98,9 +109,9 @@
* If the file created by {@link #writeTempCacheFile(InputStream, FileExtension)} was successfully parsed,
* this should be called to remove the temporary part of its name which will allow it to be a cache hit in the future.
*/
- void renameTempFile(FileExtension extension) {
+ void renameTempFile(String url, FileExtension extension) {
String fileName = filenameForUrl(url, extension, true);
- File file = new File(appContext.getCacheDir(), fileName);
+ File file = new File(parentDir(), fileName);
String newFileName = file.getAbsolutePath().replace(".temp", "");
File newFile = new File(newFileName);
boolean renamed = file.renameTo(newFile);
@@ -116,17 +127,28 @@
*/
@Nullable
private File getCachedFile(String url) throws FileNotFoundException {
- File jsonFile = new File(appContext.getCacheDir(), filenameForUrl(url, FileExtension.JSON, false));
+ File jsonFile = new File(parentDir(), filenameForUrl(url, FileExtension.JSON, false));
if (jsonFile.exists()) {
return jsonFile;
}
- File zipFile = new File(appContext.getCacheDir(), filenameForUrl(url, FileExtension.ZIP, false));
+ File zipFile = new File(parentDir(), filenameForUrl(url, FileExtension.ZIP, false));
if (zipFile.exists()) {
return zipFile;
}
return null;
}
+ private File parentDir() {
+ File file = new File(appContext.getCacheDir(), "lottie_network_cache");
+ if (file.isFile()) {
+ file.delete();
+ }
+ if (!file.exists()) {
+ file.mkdirs();
+ }
+ return file;
+ }
+
private static String filenameForUrl(String url, FileExtension extension, boolean isTemp) {
return "lottie_cache_" + url.replaceAll("\\W+", "") + (isTemp ? extension.tempExtension(): extension.extension);
}
diff --git a/lottie/src/main/java/com/airbnb/lottie/network/NetworkFetcher.java b/lottie/src/main/java/com/airbnb/lottie/network/NetworkFetcher.java
index 028ee95..2f74633 100644
--- a/lottie/src/main/java/com/airbnb/lottie/network/NetworkFetcher.java
+++ b/lottie/src/main/java/com/airbnb/lottie/network/NetworkFetcher.java
@@ -25,16 +25,20 @@
private final Context appContext;
private final String url;
- private final NetworkCache networkCache;
+ @Nullable private final NetworkCache networkCache;
- public static LottieResult<LottieComposition> fetchSync(Context context, String url) {
- return new NetworkFetcher(context, url).fetchSync();
+ public static LottieResult<LottieComposition> fetchSync(Context context, String url, @Nullable String cacheKey) {
+ return new NetworkFetcher(context, url, cacheKey).fetchSync();
}
- private NetworkFetcher(Context context, String url) {
+ private NetworkFetcher(Context context, String url, @Nullable String cacheKey) {
appContext = context.getApplicationContext();
this.url = url;
- networkCache = new NetworkCache(appContext, url);
+ if (cacheKey == null) {
+ networkCache = null;
+ } else {
+ networkCache = new NetworkCache(appContext);
+ }
}
@WorkerThread
@@ -54,7 +58,10 @@
@Nullable
@WorkerThread
private LottieComposition fetchFromCache() {
- Pair<FileExtension, InputStream> cacheResult = networkCache.fetch();
+ if (networkCache == null) {
+ return null;
+ }
+ Pair<FileExtension, InputStream> cacheResult = networkCache.fetch(url);
if (cacheResult == null) {
return null;
}
@@ -83,7 +90,7 @@
}
@WorkerThread
- private LottieResult fetchFromNetworkInternal() throws IOException {
+ private LottieResult<LottieComposition> fetchFromNetworkInternal() throws IOException {
Logger.debug("Fetching " + url);
@@ -95,7 +102,7 @@
if (connection.getErrorStream() != null || connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
String error = getErrorFromConnection(connection);
- return new LottieResult<>(new IllegalArgumentException("Unable to fetch " + url + ". Failed with " + connection.getResponseCode() + "\n" + error));
+ return new LottieResult<LottieComposition>(new IllegalArgumentException("Unable to fetch " + url + ". Failed with " + connection.getResponseCode() + "\n" + error));
}
LottieResult<LottieComposition> result = getResultFromConnection(connection);
@@ -134,7 +141,7 @@
private LottieResult<LottieComposition> getResultFromConnection(HttpURLConnection connection) throws IOException {
File file;
FileExtension extension;
- LottieResult<LottieComposition> result = null;
+ LottieResult<LottieComposition> result;
String contentType = connection.getContentType();
if (contentType == null) {
// Assume JSON for best effort parsing. If it fails, it will just deliver the parse exception
@@ -144,17 +151,25 @@
if (contentType.contains("application/zip")) {
Logger.debug("Handling zip response.");
extension = FileExtension.ZIP;
- file = networkCache.writeTempCacheFile(connection.getInputStream(), extension);
- result = LottieCompositionFactory.fromZipStreamSync(new ZipInputStream(new FileInputStream(file)), url);
+ if (networkCache == null) {
+ result = LottieCompositionFactory.fromZipStreamSync(new ZipInputStream(connection.getInputStream()), null);
+ } else {
+ file = networkCache.writeTempCacheFile(url, connection.getInputStream(), extension);
+ result = LottieCompositionFactory.fromZipStreamSync(new ZipInputStream(new FileInputStream(file)), url);
+ }
} else {
Logger.debug("Received json response.");
extension = FileExtension.JSON;
- file = networkCache.writeTempCacheFile(connection.getInputStream(), extension);
- result = LottieCompositionFactory.fromJsonInputStreamSync(new FileInputStream(new File(file.getAbsolutePath())), url);
+ if (networkCache == null) {
+ result = LottieCompositionFactory.fromJsonInputStreamSync(connection.getInputStream(), null);
+ } else {
+ file = networkCache.writeTempCacheFile(url, connection.getInputStream(), extension);
+ result = LottieCompositionFactory.fromJsonInputStreamSync(new FileInputStream(new File(file.getAbsolutePath())), url);
+ }
}
- if (result.getValue() != null) {
- networkCache.renameTempFile(extension);
+ if (networkCache != null && result.getValue() != null) {
+ networkCache.renameTempFile(url, extension);
}
return result;
}
diff --git a/lottie/src/test/java/com/airbnb/lottie/LottieCompositionFactoryTest.java b/lottie/src/test/java/com/airbnb/lottie/LottieCompositionFactoryTest.java
index 7693858..7fee47d 100644
--- a/lottie/src/test/java/com/airbnb/lottie/LottieCompositionFactoryTest.java
+++ b/lottie/src/test/java/com/airbnb/lottie/LottieCompositionFactoryTest.java
@@ -9,9 +9,9 @@
import org.robolectric.RuntimeEnvironment;
import java.io.FileNotFoundException;
-import java.io.StringReader;
+import java.io.IOException;
+import java.io.InputStream;
-import static com.airbnb.lottie.parser.moshi.JsonReader.of;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
@@ -20,6 +20,7 @@
import static okio.Okio.source;
import static org.junit.Assert.assertTrue;
+@SuppressWarnings("ReferenceEquality")
public class LottieCompositionFactoryTest extends BaseTest {
private static final String JSON = "{\"v\":\"4.11.1\",\"fr\":60,\"ip\":0,\"op\":180,\"w\":300,\"h\":300,\"nm\":\"Comp 1\",\"ddd\":0,\"assets\":[]," +
"\"layers\":[{\"ddd\":0,\"ind\":1,\"ty\":4,\"nm\":\"Shape Layer 1\",\"sr\":1,\"ks\":{\"o\":{\"a\":0,\"k\":100,\"ix\":11},\"r\":{\"a\":0," +
@@ -90,7 +91,7 @@
@Test
public void testNullMultipleTimesAsync() {
- JsonReader reader = JsonReader.of(buffer(source(new StringInputStream(JSON))));
+ JsonReader reader = JsonReader.of(buffer(source(getNeverCompletingInputStream())));
LottieTask<LottieComposition> task1 = LottieCompositionFactory.fromJsonReader(reader, null);
LottieTask<LottieComposition> task2 = LottieCompositionFactory.fromJsonReader(reader, null);
assertFalse(task1 == task2);
@@ -98,7 +99,7 @@
@Test
public void testNullMultipleTimesSync() {
- JsonReader reader = JsonReader.of(buffer(source(new StringInputStream(JSON))));
+ JsonReader reader = JsonReader.of(buffer(source(getNeverCompletingInputStream())));
LottieResult<LottieComposition> task1 = LottieCompositionFactory.fromJsonReaderSync(reader, null);
LottieResult<LottieComposition> task2 = LottieCompositionFactory.fromJsonReaderSync(reader, null);
assertFalse(task1 == task2);
@@ -106,7 +107,7 @@
@Test
public void testCacheWorks() {
- JsonReader reader = JsonReader.of(buffer(source(new StringInputStream(JSON))));
+ JsonReader reader = JsonReader.of(buffer(source(getNeverCompletingInputStream())));
LottieTask<LottieComposition> task1 = LottieCompositionFactory.fromJsonReader(reader, "foo");
LottieTask<LottieComposition> task2 = LottieCompositionFactory.fromJsonReader(reader, "foo");
assertTrue(task1 == task2);
@@ -114,7 +115,7 @@
@Test
public void testZeroCacheWorks() {
- JsonReader reader = JsonReader.of(buffer(source(new StringInputStream(JSON))));
+ JsonReader reader = JsonReader.of(buffer(source(getNeverCompletingInputStream())));
LottieCompositionFactory.setMaxCacheSize(1);
LottieResult<LottieComposition> taskFoo1 = LottieCompositionFactory.fromJsonReaderSync(reader, "foo");
LottieResult<LottieComposition> taskBar = LottieCompositionFactory.fromJsonReaderSync(reader, "bar");
@@ -126,4 +127,12 @@
public void testCannotSetCacheSizeToZero() {
LottieCompositionFactory.setMaxCacheSize(0);
}
+
+ private static InputStream getNeverCompletingInputStream() {
+ return new InputStream() {
+ @Override public int read() throws IOException {
+ return 100;
+ }
+ };
+ }
}