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 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 {

@Nullable final LottieNetworkFetcher networkFetcher;
@Nullable final LottieNetworkCacheProvider cacheProvider;

private LottieConfig(@Nullable LottieNetworkFetcher networkFetcher, @Nullable LottieNetworkCacheProvider cacheProvider) {
this.networkFetcher = networkFetcher;
this.cacheProvider = cacheProvider;
}

public static final class Builder {

@Nullable
private LottieNetworkFetcher networkFetcher;
@Nullable
private LottieNetworkCacheProvider cacheProvider;

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

@NonNull
public Builder setCacheDir(@NonNull final File file) {
cacheProvider = new LottieNetworkCacheProvider() {
@Override @NonNull public File getCacheDir() {
if (!file.isDirectory()) {
throw new IllegalArgumentException("cache file must be a directory");
}
return file;
}
};
return this;
}

@NonNull
public Builder setCacheProvider(@NonNull final LottieNetworkCacheProvider fileCacheProvider) {
cacheProvider = new LottieNetworkCacheProvider() {
@NonNull @Override public File getCacheDir() {
File file = fileCacheProvider.getCacheDir();
if (!file.isDirectory()) {
throw new IllegalArgumentException("cache file must be a directory");
}
return file;
}
};
return this;
}

@NonNull
public LottieConfig build() {
return new LottieConfig(networkFetcher, cacheProvider);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.airbnb.lottie.network;

import com.airbnb.lottie.utils.Logger;

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

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

public class DefaultLottieFetchResult implements LottieFetchResult {

@NonNull
private final HttpURLConnection connection;

public DefaultLottieFetchResult(@NonNull HttpURLConnection connection) {
this.connection = connection;
}

@Override public boolean isSuccessful() {
try {
return connection.getResponseCode() / 100 == 2;
} catch (IOException e) {
return false;
}
}

@NonNull @Override public InputStream bodyByteStream() throws IOException {
return connection.getInputStream();
}

@Nullable @Override public String contentType() {
return connection.getContentType();
}

@Nullable @Override public String error() {
try {
return isSuccessful() ? null :
"Unable to fetch " + connection.getURL() + ". Failed with " + connection.getResponseCode() + "\n" + getErrorFromConnection(connection);
} catch (IOException e) {
Logger.warning("get error failed ", e);
return e.getMessage();
}
}

@Override public void close() {
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,19 @@
package com.airbnb.lottie.network;

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

import androidx.annotation.NonNull;

public class DefaultLottieNetworkFetcher implements LottieNetworkFetcher {

@Override
@NonNull
public LottieFetchResult fetchSync(@NonNull String url) throws IOException {
final HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could this be HttpsUrlConnection?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

HttpUrlConnection was already in your library. If we change it to HttpsUrlConnection, won't anything break?

Copy link
Collaborator

Choose a reason for hiding this comment

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

HttpsUrlConnection is also part of the Android SDK but it extends HttpUrlConnection so you're good here.

connection.setRequestMethod("GET");
connection.connect();
return new DefaultLottieFetchResult(connection);
}
}
Loading