Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added NetworkFetcher and NetworkCache customization #1629

Merged
merged 17 commits into from
Oct 19, 2020
57 changes: 57 additions & 0 deletions lottie/src/main/java/com/airbnb/lottie/L.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
package com.airbnb.lottie;

import android.content.Context;

import com.airbnb.lottie.network.CacheProvider;
import com.airbnb.lottie.network.DefaultFetcher;
import com.airbnb.lottie.network.Fetcher;
import com.airbnb.lottie.network.NetworkCache;
import com.airbnb.lottie.network.NetworkFetcher;

import java.io.File;

import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.core.os.TraceCompat;

Expand All @@ -16,6 +27,12 @@ public class L {
private static int traceDepth = 0;
private static int depthPastMaxDepth = 0;

private static Fetcher fetcher;
private static CacheProvider cacheProvider;

private static volatile NetworkFetcher networkFetcher;
private static volatile NetworkCache networkCache;

public static void setTraceEnabled(boolean enabled) {
if (traceEnabled == enabled) {
return;
Expand Down Expand Up @@ -60,4 +77,44 @@ public static float endSection(String section) {
TraceCompat.endSection();
return (System.nanoTime() - startTimeNs[traceDepth]) / 1000000f;
}

public static void setFetcher(Fetcher customFetcher) {
fetcher = customFetcher;
}

public static void setCacheProvider(CacheProvider customProvider) {
cacheProvider = customProvider;
}

@NonNull
public static NetworkFetcher networkFetcher(@NonNull Context context) {
NetworkFetcher local = networkFetcher;
if (local == null) {
synchronized (NetworkFetcher.class) {
local = networkFetcher;
if (local == null) {
networkFetcher = local = new NetworkFetcher(networkCache(context), fetcher != null ? fetcher : new DefaultFetcher());
}
}
}
return local;
}

@NonNull
public static NetworkCache networkCache(@NonNull final Context context) {
NetworkCache local = networkCache;
if (local == null) {
synchronized (NetworkCache.class) {
local = networkCache;
if (local == null) {
networkCache = local = new NetworkCache(cacheProvider != null ? cacheProvider : new CacheProvider() {
@Override @NonNull public File getCacheDir() {
return new File(context.getCacheDir(), "lottie_network_cache");
}
});
}
}
}
return local;
}
}
17 changes: 17 additions & 0 deletions lottie/src/main/java/com/airbnb/lottie/Lottie.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.airbnb.lottie;

import androidx.annotation.NonNull;

/**
* Class for initializing the library with custom config
*/
public class Lottie {
egroden marked this conversation as resolved.
Show resolved Hide resolved

private Lottie() {
}

public static void initialize(@NonNull final LottieConfig lottieConfig) {
L.setFetcher(lottieConfig.networkFetcher);
L.setCacheProvider(lottieConfig.cacheProvider);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,13 @@
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
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;

import com.airbnb.lottie.utils.Utils;

import org.json.JSONObject;

import java.io.ByteArrayInputStream;
Expand Down Expand Up @@ -65,14 +63,14 @@ public static void setMaxCacheSize(int size) {
public static void clearCache(Context context) {
taskCache.clear();
LottieCompositionCache.getInstance().clear();
new NetworkCache(context).clear();
L.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
* might need an animation in the future.
*
* <p>
* To skip the cache, add null as a third parameter.
*/
public static LottieTask<LottieComposition> fromUrl(final Context context, final String url) {
Expand All @@ -88,7 +86,7 @@ public static LottieTask<LottieComposition> fromUrl(final Context context, final
return cache(cacheKey, new Callable<LottieResult<LottieComposition>>() {
@Override
public LottieResult<LottieComposition> call() {
return NetworkFetcher.fetchSync(context, url, cacheKey);
return L.networkFetcher(context).fetchSync(url, cacheKey);
}
});
}
Expand All @@ -111,14 +109,14 @@ public static LottieResult<LottieComposition> fromUrlSync(Context context, Strin
*/
@WorkerThread
public static LottieResult<LottieComposition> fromUrlSync(Context context, String url, @Nullable String cacheKey) {
return NetworkFetcher.fetchSync(context, url, cacheKey);
return L.networkFetcher(context).fetchSync(url, 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.
*
* <p>
* To skip the cache, add null as a third parameter.
*
* @see #fromZipStream(ZipInputStream, String)
Expand All @@ -132,7 +130,7 @@ public static LottieTask<LottieComposition> fromAsset(Context context, final Str
* 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.
*
* <p>
* Pass null as the cache key to skip the cache.
*
* @see #fromZipStream(ZipInputStream, String)
Expand All @@ -152,22 +150,22 @@ public LottieResult<LottieComposition> call() {
* 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.
*
* <p>
* To skip the cache, add null as a third parameter.
*
* @see #fromZipStreamSync(ZipInputStream, String)
*/
@WorkerThread
public static LottieResult<LottieComposition> fromAssetSync(Context context, String fileName) {
String cacheKey = "asset_" + fileName;
return fromAssetSync(context, fileName, cacheKey);
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.
*
* <p>
* Pass null as the cache key to skip the cache.
*
* @see #fromZipStreamSync(ZipInputStream, String)
Expand All @@ -191,7 +189,7 @@ public static LottieResult<LottieComposition> fromAssetSync(Context context, Str
* 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.
*
* <p>
* To skip the cache, add null as a third parameter.
*/
public static LottieTask<LottieComposition> fromRawRes(Context context, @RawRes final int rawRes) {
Expand All @@ -204,7 +202,7 @@ public static LottieTask<LottieComposition> fromRawRes(Context context, @RawRes
* 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.
*
* <p>
* Pass null as the cache key to skip caching.
*/
public static LottieTask<LottieComposition> fromRawRes(Context context, @RawRes final int rawRes, @Nullable final String cacheKey) {
Expand All @@ -227,7 +225,7 @@ public LottieResult<LottieComposition> call() {
* 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.
*
* <p>
* To skip the cache, add null as a third parameter.
*/
@WorkerThread
Expand All @@ -241,7 +239,7 @@ public static LottieResult<LottieComposition> fromRawResSync(Context context, @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.
*
* <p>
* Pass null as the cache key to skip caching.
*/
@WorkerThread
Expand Down
68 changes: 68 additions & 0 deletions lottie/src/main/java/com/airbnb/lottie/LottieConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.airbnb.lottie;

import android.content.Context;

import com.airbnb.lottie.network.Fetcher;
import com.airbnb.lottie.network.CacheProvider;

import java.io.File;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

/**
* Class for custom library configuration
*/
public class LottieConfig {

@NonNull final Context applicationContext;
@Nullable final Fetcher networkFetcher;
@Nullable final CacheProvider cacheProvider;

public LottieConfig(@NonNull Context applicationContext, @Nullable Fetcher networkFetcher, @Nullable CacheProvider cacheProvider) {
this.applicationContext = applicationContext;
this.networkFetcher = networkFetcher;
this.cacheProvider = cacheProvider;
}

public static final class Builder {

@NonNull
private final Context context;
@Nullable
private Fetcher networkFetcher;
@Nullable
private CacheProvider cacheProvider;

public Builder(@NonNull Context context) {
this.context = context.getApplicationContext();
}

@NonNull
public Builder setNetworkFetcher(@NonNull Fetcher fetcher) {
this.networkFetcher = fetcher;
return this;
}

@NonNull
public Builder setCacheDir(@NonNull final File file) {
this.cacheProvider = new CacheProvider() {
@Override @NonNull public File getCacheDir() {
return file;
}
};
return this;
}

@NonNull
public Builder setCacheProvider(@NonNull CacheProvider fileCacheProvider) {
this.cacheProvider = fileCacheProvider;
return this;
}

@NonNull
public LottieConfig build() {
return new LottieConfig(context, networkFetcher, cacheProvider);
}
}
}
18 changes: 18 additions & 0 deletions lottie/src/main/java/com/airbnb/lottie/network/CacheProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.airbnb.lottie.network;

import java.io.File;

import androidx.annotation.NonNull;

/**
* Interface for providing the custom cache directory
*/
public interface CacheProvider {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Javadoc. Also indicate when getCacheDir() is called and whether it is called every time or cached.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's name this LottieNetworkCacheProvider
and in the docs specify that this is the cache where animations downloaded via url are saved.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


/**
* Called during cache operations
*
* @return cache directory
*/
@NonNull File getCacheDir();
}
54 changes: 54 additions & 0 deletions lottie/src/main/java/com/airbnb/lottie/network/DefaultFetcher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.airbnb.lottie.network;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

import androidx.annotation.NonNull;

public class DefaultFetcher implements Fetcher {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would become DefaultLottieNetworkFetcher

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


@Override
@NonNull
public LottieNetworkResult fetchSync(@NonNull String url) throws IOException {
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
connection.setRequestMethod("GET");

try {
connection.connect();

if (connection.getErrorStream() != null || connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
String error = getErrorFromConnection(connection);
return new LottieNetworkResult.Error("Unable to fetch " + url + ". Failed with " + connection.getResponseCode() + "\n" + error,
connection.getResponseCode());
}

return new LottieNetworkResult.Success(connection.getInputStream(), connection.getContentType());
} finally {
connection.disconnect();
}
}

private String getErrorFromConnection(HttpURLConnection connection) throws IOException {
BufferedReader r = new BufferedReader(new InputStreamReader(connection.getErrorStream()));
StringBuilder error = new StringBuilder();
String line;

try {
while ((line = r.readLine()) != null) {
error.append(line).append('\n');
}
} catch (Exception e) {
throw e;
} finally {
try {
r.close();
} catch (Exception e) {
// Do nothing.
}
}
return error.toString();
}
}
15 changes: 15 additions & 0 deletions lottie/src/main/java/com/airbnb/lottie/network/Fetcher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.airbnb.lottie.network;

import java.io.IOException;

import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;

/**
* Interface for custom fetcher
*/
public interface Fetcher {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's name this LottieNetworkFetcher to make it explicit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@WorkerThread
@NonNull
LottieNetworkResult fetchSync(@NonNull String url) throws IOException;
}
Loading