-
Notifications
You must be signed in to change notification settings - Fork 6.1k
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
Display cached image while requesting new image #1257
Comments
Add a thumbnail load that does something similar. You can create a custom model which you likely already have and have a field to decide if you want to return the cached or not. |
Thanks @TWiStErRob, I'm not sure how to do a thumbnail load. I was contemplating using downloadOnly to manually refresh the cache if needed before hand. I also tried using a listener to reload the image. However that didn't work so well. |
Can you share your current related code? |
HTTP Response Caching with OkHttpSo to enable HTTP response caching with OkHttp I extend the GlideModule. This is based on your suggestion in another thread regarding custom OkHttp configuration. public class CustomGlideModule implements GlideModule {
public static final int IMAGE_CACHE_SIZE = 10 * 1024 * 1024;
@Override
public void applyOptions(Context context, GlideBuilder builder) {
}
@Override
public void registerComponents(Context context, Glide glide) {
OkHttpClient client = new OkHttpClient().newBuilder().addNetworkInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Response originalResponse = chain.proceed(chain.request());
// I need to set the Cache-Control in order to force server side validation for the ETAG
return originalResponse.newBuilder().header("Cache-Control", "max-age=0").build();
}
}).cache(new Cache(context.getCacheDir(), IMAGE_CACHE_SIZE)).build();
glide.register(GlideUrl.class, InputStream.class,
new com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader.Factory(client));
}
} I then use Glide with caching disabled so that the underlying network layer (OkHttp) always contacts the server. DrawableRequestBuilder builder = Glide.with(context)
.load(Uri.parse("http://sample_image.jpg"))
.error(drawableResource)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.placeholder(R.drawable.placeholder)
.crossFade()
.thumbnail(0.3f);
builder.into(target); Placeholder ProblemHowever this results in the placeholder image always being shown while the network call is being made. This is why I'd like to load the cached image from OkHttp as the placeholder. Possible Solution using Glide ListenersI tried to fix this by using Glide listeners. First I load the image with Glide caching enabled. If the image isn't cached, it's fetched from the server and then stored. If it is cached it is loaded immediately and the server isn't contacted. I then have a listener for when this first load completes. If the load occurred from memory (ie. a cached image) it attempts another DrawableRequestBuilder builder = Glide.with(context.getApplicationContext())
.load(Uri.parse("http://sample_image.jpg"))
.error(drawable)
.placeholder(R.drawable.placeholder)
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
.crossFade()
.thumbnail(0.3f)
.listener(new RequestListener<Uri, GlideDrawable>() {
@Override
public boolean onException(Exception e, Uri model, Target<GlideDrawable> target, boolean isFirstResource) {
return false;
}
@Override
public boolean onResourceReady(GlideDrawable oldResource, Uri model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
if (isFromMemoryCache) {
DrawableRequestBuilder builder = Glide.with(context.getApplicationContext())
.load(Uri.parse("http://sample_image.jpg"))
.error(oldResource)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.placeholder(oldResource)
.crossFade()
.dontAnimate()
.thumbnail(0.3f);
builder.into(target);
return true;
}
return false;
}
});
builder.into(target); I'm hoping there's a more elegant solution. Perhaps you have a better idea @TWiStErRob |
Take a look at the above commit. Here's what I managed to achieve:
For visibility I added a grayscale transform so it's easy to see which one is cached and which one is the real deal. The server side is a simple PHP script handling ETag validation manually and generating a new image every 5 seconds. Be careful with Here's the relevant code: public class CachedGlideUrl extends GlideUrl {
public CachedGlideUrl(String url) { super(url); }
}
public class ForceLoadGlideUrl extends GlideUrl {
private static final Headers FORCE_ETAG_CHECK = new LazyHeaders.Builder()
// I need to set the Cache-Control in order to force server side validation for the ETAG
.addHeader("Cache-Control", "max-age=0")
.build();
public ForceLoadGlideUrl(String url) { super(url, FORCE_ETAG_CHECK); }
}
@Override public void registerComponents(Context context, Glide glide) {
final Cache cache = new Cache(new File(context.getCacheDir(), "okhttp"), IMAGE_CACHE_SIZE);
OkHttpClient client = new OkHttpClient().newBuilder().cache(cache).build();
glide.register(CachedGlideUrl.class, InputStream.class,
superFactory(new OkHttpUrlLoader.Factory(client), CachedGlideUrl.class));
glide.register(ForceLoadGlideUrl.class, InputStream.class,
superFactory(new OkHttpUrlLoader.Factory(client), ForceLoadGlideUrl.class));
}
@SuppressWarnings({"unchecked", "unused"})
private static <T> ModelLoaderFactory<T, InputStream> superFactory(
ModelLoaderFactory<? super T, InputStream> factory, Class<T> modelType) {
return (ModelLoaderFactory<T, InputStream>)factory;
}
Glide
.with(this)
.load(new ForceLoadGlideUrl(urlString))
.fitCenter()
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true)
.thumbnail(Glide
.with(this)
.load(new CachedGlideUrl(urlString))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true)
.bitmapTransform(new FitCenter(context), new GrayscaleTransformation(context))
.sizeMultiplier(0.25f)
)
.into(imageView)
; The trick I applied is using separate models for different behavior. This way Glide's default behavior is also preserved and available, that is: not every request is cached, only those loaded via these models. If you want to use the same OkHttpClient for all loads just add a register for The NPE you mention seems weird, because target is already initialized at the point of the outer |
Thanks @TWiStErRob, I'm not sure how to setup the superFactory. I do like the fact that we can do network agnostic header injection. Is it possible to add this to the wiki? |
You can find a full working example in my support project. There's a link just above my previous comment. This feature was introduced in https://github.com/bumptech/glide/releases/tag/v3.6.0 |
Sorry, completely missed that. Thanks, I managed to get working and the transition is a lot smoother from the old image to the new one. However I'm not sure if the |
If you check the PHP file I was using to test, I had to respond with below the first time:
only then OkHttp was willing to revalidate when using the forced version. The default behaviour is to cache the image and OkHttp will respond from cache, but then if you add the |
With regard to @TWiStErRob's solution, can the same thing be achieved using Glide 4's |
I've read through the large number of caching questions and have Glide working perfectly with OkHTTP to achieve the 304 Not Modified use case (described in #463). Basically the app always does a network call, but only downloads the new image if the etag changes. Otherwise, it uses the cached version from OkHTTP. In order to achieve this I use DiskCacheStrategy.NONE and delegate all caching to OkHTTP. My question is, is there a simple way to use the cached image as a placeholder image? #463 doesn't explain how to achieve it.
The text was updated successfully, but these errors were encountered: