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.LottieNetworkCacheProvider;
import com.airbnb.lottie.network.DefaultLottieNetworkFetcher;
import com.airbnb.lottie.network.LottieNetworkFetcher;
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 LottieNetworkFetcher fetcher;
private static LottieNetworkCacheProvider 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(LottieNetworkFetcher customFetcher) {
fetcher = customFetcher;
}

public static void setCacheProvider(LottieNetworkCacheProvider 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 DefaultLottieNetworkFetcher());
}
}
}
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 LottieNetworkCacheProvider() {
@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
69 changes: 69 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,69 @@
package com.airbnb.lottie;

import android.content.Context;

import com.airbnb.lottie.network.LottieNetworkFetcher;
import com.airbnb.lottie.network.LottieNetworkCacheProvider;

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 LottieNetworkFetcher networkFetcher;
@Nullable final LottieNetworkCacheProvider cacheProvider;

public LottieConfig(@NonNull Context applicationContext, @Nullable LottieNetworkFetcher networkFetcher, @Nullable
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think you meant for this to be private

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

LottieNetworkCacheProvider cacheProvider) {
this.applicationContext = applicationContext;
this.networkFetcher = networkFetcher;
this.cacheProvider = cacheProvider;
}

public static final class Builder {

@NonNull
private final Context context;
@Nullable
private LottieNetworkFetcher networkFetcher;
@Nullable
private LottieNetworkCacheProvider cacheProvider;

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

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

@NonNull
public Builder setCacheDir(@NonNull final File file) {
this.cacheProvider = new LottieNetworkCacheProvider() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Throw IllegalArgumentExcception if !file.isDirectory

Also, this.cacheProvider can be cacheProvider

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 File getCacheDir() {
return file;
}
};
return this;
}

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

@NonNull
public LottieConfig build() {
return new LottieConfig(context, networkFetcher, cacheProvider);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
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;
import androidx.annotation.Nullable;

public class DefaultLottieNetworkFetcher implements LottieNetworkFetcher {

@Nullable
private HttpURLConnection connection;

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

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());
}

@Override public void disconnect() {
if (connection != null) {
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();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.airbnb.lottie.network;


import java.io.File;

import androidx.annotation.NonNull;

/**
* Interface for providing the custom cache directory where animations downloaded via url are saved.
* @see com.airbnb.lottie.Lottie#initialize
*/
public interface LottieNetworkCacheProvider {

/**
* Called during cache operations
*
* @return cache directory
*/
@NonNull File getCacheDir();
gpeal marked this conversation as resolved.
Show resolved Hide resolved
}
Loading