i'm using volley to load my images and cache them.
mImageLoader = new ImageLoader(getRequestQueue(context), mImageCache);
which mImageCache is a DiskLruImageCache.
volley fetches images from server by ImageRequest which extend the ImageRequest<Bitmap>
and in request class there is boolean that defines whether to cache the response or not
/** Whether or not responses to this request should be cached. */
private boolean mShouldCache = true;
and ImageRequest hasn't disabled mShouldCache.
as you can see the default value is true so after volley fetches an image caches it under the volley cache directory by diskBasedCache.
so now i have to cache bitmap one from ImageRequest and one from ImageLoader how can i disable ImageRequest cache ? or any other suggestions ?
You are making a mistake giving the ImageLoader a disk cache. Volley already has a shared disk cache for every response, be it an image are not, that works according to HTTP cache headers by default.
You are supposed to provide a memory bitmap cache to the ImageLaoder. Look at the documentation.
The reasoning for it is how Volley is designed. This is the image request logic for Volley:
Image with url X is added to the queue.
Check image memory cache (provided by you) - If available, return bitmap. quickest
Check shared disk cache - If available, check cache headers to see that image is still valid. If valid - add to memory bitmap cache and return. slower, but still pretty quick
This step means that either the image was in the disk cache but its cache headers are missing or expired, or the image wasn't available in the cache at all. Either way, Volley performs a network request and caches the response in both caches. slowest
So by providing a disk cache - you are both slowing down your app and taking up to twice as much disk space with redundant image saving.
Use a memory cache.
Related
I am working on an app with a lot of dynamic and changing content.
I pull all my data from my server when the app is loading.
As a result, nearly every activity/fragment is loading separately which will cause the user to wait a lot of time for each "page" to load individually.
My goal is to create one loading page when the app starts while being responssible for all the downloading and will disk cache all the images and info(strings) and ti pull them at the right time. (or at least to most of it)
I had the chance to use retrofit, okhttp and Picasso as a single additional library, I know though that they can work together and to be synced and that disk cacheing is available through at least two of this libraries (picasso and okhttp) I'm not sure though which one should do which part and how I can sync them together.
I will appreciate every Tip/Guidance, thanks ahead.
okhttp provides support for cache control headers. I've implemented them in an app before to provide a cache when network is flaky using this guide like so:
int cacheSize = 10 * 1024 * 1024; // 10 MiB
Cache cache = new Cache(cacheDirectory, cacheSize);
client = new OkHttpClient.Builder()
.cache(cache)
.build();
As Retrofit uses okhttp internally (if you're using the latest at least), you don't configure any caching for it. Just use the okhttp client you just configured:
RestAdapter restAdapter = new RestAdapter.Builder()
.setClient(new OkClient(client))
.setServer("http://example.com")
...
.build();
Picasso automatically caches images using some default cache size limit. You can change Picasso's default, and I've found some answers here and here. You could set the cache size in the onCreate of your application:
Picasso.Builder builder = new Picasso.Builder(this);
builder.downloader(new OkHttpDownloader(this,Integer.MAX_VALUE));
Picasso picasso = builder.build();
picasso.setIndicatorsEnabled(true);
picasso.setLoggingEnabled(true);
Picasso.setSingletonInstance(picasso);
Picasso also lets you prefetch images earlier on in an app's lifecycle if you have the time to begin with (say, on a loading screen) and want to make later parts of the app load quicker. To do that, I would use the fetch method from the Picasso builder to get the images, but not insert them into any ImageViews. You can Google it too, but there's a quick answer here which explains the background behind this:
Picasso.with(getApplicationContext())
.load(url)
.fetch();
IIRC you need to make sure you fetch the same sized and transformed image as you try and retrieve later, because Picasso caches the transformed image result rather than the raw downloaded image.
Im using Picasso and okhttp to download and cache images onto the disk.
Im using the below code snippet to set a cache of 10MB to the okhttp client and using it for Picasso.
File folder = new File(context.getCacheDir(),"HomePageCache");
if (!folder.exists())
folder.mkdir();
okHttpClient.setCache(new com.squareup.okhttp.Cache(folder, 1024 * 1024 * 10));
OkHttpDownloader okHttpDownloader = new OkHttpDownloader(okHttpClient);
picasso = new com.squareup.picasso.Picasso.Builder(context).indicatorsEnabled(true).downloader(okHttpDownloader).build();
This is working perfectly most of the time caching and reloading images even when offline , but the problem is that sometimes it just clears the image cache completely.
I am checking the size of the image cache just to be sure, and it has never crossed 2 MB.
Is there any other reason why my cache is getting cleared?
Like Nikola pointed out in the comment, the cache expire header was responsible for invalidating cached images.
Scenario:
I have a large GIF image which I want to cache the first time user opens the app using Glide - Image Loading and Caching library. After that whenever user opens the app, I want to show the cached version if present. This GIF URL will expire after a given interval. When it expires, I fetch the new GIF URL and display/cache that for future use.
What I tried:
I went through Caching and Cache Invalidation on Glide's github page. I also went though the Google Group thread Ensuring That Images Loaded Only Come From Disk Cache, which shows how to get the image form cache. I also went through How to invalidate Glide cache for some specific images question.
From the links above I see the following code sniplet which shows how to load the image from cache. However this only tries to get the image from cache. If its not present in cache, it doesn't try to get from the network and fails:
Glide.with(TheActivity.this)
.using(new StreamModelLoader<String>() {
#Override
public DataFetcher<InputStream> getResourceFetcher(final String model, int i, int i1) {
return new DataFetcher<InputStream>() {
#Override
public InputStream loadData(Priority priority) throws Exception {
throw new IOException();
}
#Override
public void cleanup() {
}
#Override
public String getId() {
return model;
}
#Override
public void cancel() {
}
};
}
})
.load("http://sampleurl.com/sample.gif")
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
.into(theImageView);
Questions:
Is there a cleaner way to achieve the following: Show the GIF image from the cache if present else download the GIF, cache it for later use and show it in the ImageView.
The caching article above mentions the following:
In practice, the best way to invalidate a cache file is to change
your identifier when the content changes (url, uri, file path etc)
The server sends a different URL to the app when the previous one expires. In this case, I believe the old image will eventually be Garbage Collected? Is there a way to force remove the image from the cache?
On a similar note, is there a way to prevent the Garbage Collection of an image with specific key (to prevent downloading the large file again) and then later instruct to delete the old image from cache when the URL changes?
You don't need a custom ModelLoader to show the GIF from cache if present and fetch it otherwise, that's actually Glide's default behavior. Just using a standard load line should work fine:
Glide.with(TheActivity.this)
.load("http://sampleurl.com/sample.gif")
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
.into(theImageView);
Your code will prevent Glide from downloading the GIF and will only show the GIF if it is already cached, which it sounds like you don't want.
Yes, the old image will eventually be removed. By default Glide uses an LRU cache, so when the cache is full, the least recently used image will be removed. You can easily customize the size of the cache to help this along if you want. See the Configuration wiki page for how to change the cache size.
Unfortunately there isn't any way to influence the contents of the cache directly. You cannot either remove an item explicitly, or force one to be kept. In practice with an appropriate disk cache size you usually don't need to worry about doing either. If you display your image often enough, it won't be evicted. If you try to cache additional items and run out of space in the cache, older items will be evicted automatically to make space.
Glide.with(context)
.load("http://sampleurl.com/sample.gif")
.skipMemoryCache(true)
.into(imageView);
You already noticed that we called .skipMemoryCache(true) to specifically tell Glide to skip the memory cache. This means that Glide will not put the image in the memory cache. It's important to understand, that this only affects the memory cache! Glide will still utilize the disk cache to avoid another network request for the next request to the same image URL.for more read this
Glide Cache & request optimization.
Happy coding!!
I have an app that loads images on each item on a list view, and I use Volley to make life easier for me; I need to have to images loaded from disk if it's already been downloaded before.
Problem: It won't work. It needs to re-download the images all over again. I need to have the image saved even after I exit the app.
Weird: It works only on one particular image (and it has nothing to do with size)!
What I Used: I patterned this using this site: https://github.com/rdrobinson3/VolleyImageCacheExample.
I also tried this: http://howrobotswork.wordpress.com/2013/06/02/downloading-a-bitmap-asynchronously-with-volley-example/
The Code:
String godzilla = "http://vineland.pynchonwiki.com/wiki/images/c/cf/Godzilla.jpg";
//String meme = "http://upload.wikimedia.org/wikipedia/en/d/d7/Meme_Many_Journeys.jpg";
ImageCacheManager.getInstance().getImageLoader().get(godzilla, new ImageLoader.ImageListener() {
#Override
public void onResponse(ImageLoader.ImageContainer imageContainer, boolean b) {
viewHolder.backgroundImage = imageContainer.getBitmap();
updateBackgroundImage(viewHolder, viewHolder.backgroundImage, object);
updateLayoutAlignmentParams(viewHolder);
}
#Override
public void onErrorResponse(VolleyError volleyError) {
}
});
if(viewHolder.backgroundImage != null)
updateBackgroundImage(viewHolder, viewHolder.backgroundImage, object);
I've tried the meme website, and it still has problems. I had one particular site that contains an image that oddly works. Which makes it even more confusing.
Edit: Additional info, it seems like there's an error on adding lruEntries as lruEntries.remove(entry.key is being called on completeEdit().
Volley has 2 cache layers when it comes to images:
The L1 level: a memory cache provided by you in the ImageLoader constructor.
The L2 level: a disk cache that is shared among every request performed by the same RequestQueue.
The disk cache caches every response unless explicitly requested not to by the request. But, the caching is performed according to the HTTP cache headers of the response.
When you request an image, this is what Volley does:
Check L1 cache for image. Return if found.
Image not in memory - Check L2 cache. If found check expiration of the cache headers. If still valid, add to L1 cache and return image.
Image not on disk (either not there or expired) - Perform a network request. Cache the response in the disk cache and the bitmap in the memory cache and return.
I bet that the image that loaded from the disk has cache headers.
IMO, you have 3 options:
The image server is yours - add the appropriate cache headers.
The image server isn't yours - accept the fact the some images will not be cached on the disk.
Override the caching policy to better suit your needs. This means editing the Volley source code.
I'm trying to display a listview with a lot of (remote) images. I'm trying to use volley for the task.
Volley somewhat works, but not good enough. In ImageLoader.get volley has the following piece of code:
final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight);
// Try to look up the request in the cache of remote images.
Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
if (cachedBitmap != null) {
// Return the cached bitmap.
ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);
imageListener.onResponse(container, true);
return container;
}
However, getCacheKey produces a key like this:
/**
* Creates a cache key for use with the L1 cache.
* #param url The URL of the request.
* #param maxWidth The max-width of the output.
* #param maxHeight The max-height of the output.
*/
private static String getCacheKey(String url, int maxWidth, int maxHeight) {
return new StringBuilder(url.length() + 12).append("#W").append(maxWidth)
.append("#H").append(maxHeight).append(url).toString();
}
i.e. It appends some "metadata" like width and height to the key.
This key never produces a hit and if the image is not in the L1 cache it is fetched online. When the image is fetched online it is saved in the disk-cache but Volley saves it with the URL (and only the URL) as key.
Is this expected behaviour? Am I missing something?
The reason you're not getting any hits is because the default behavior in Volley for disk caching is dependent on the HTTP headers of the element you're requesting (in your case, an image).
The way Volley works is:
ImageLoader checks the L1 cache for image (memory cache provided by you to the ImageLoader in its constructor). If available return image.
Request processed by RequestQueue. It checks the L2 (disk cache) for the image.
If found in the disk cache, check the image expiry time. If not expired, return.
Download image and return it.
Save image in caches.
If you want the default settings to work, the images must have a Cache-Control header like max-age=??? where the question marks indicate enough seconds from the time it was downloaded.
If you want to change the default behavior, I'm not sure, but I think you have to edit the code a bit.
Look at the CacheDispatcher class in the Volley source.
Can you post your class that implements ImageCache.
I've just been looking at this myself and realised in my code I wasn't adding the bitmap to the memory cache when it was loading it from the disk, so it would always reload it from the disk each time.
This is a simple example of what I mean and where I was going wrong
#Override
public Bitmap getBitmap(String cachKey) {
Bitmap b = null;
//check the memory first
b = memoryCache.get(cacheKey);
if(b == null){
//memory cache was null, check file cache
b = diskLruImageCache.getBitmap(cacheKey);
// this is where it needs to be added to your memory cache
if(b != null){
memoryCache.put(url, b);
}
}
return b;
}
I tracked down this issue in my own app today. I was setting a max cache size in KB in the constructor, but reporting a size in bytes in sizeOf(), so nothing was ever cached.
This answer set me straight.
Probably you are using NetworkImageView to load your images. You can use a ImageView and ImageLoader to do the same thing. Using ImageLoader the metadata in the key is like "#W0#H0" for any image size.
ImageLoader imageLoader = getImageLoader();
imageLoader.get(url, ImageLoader.getImageListener(imageView, defaultDrawable, errorDrawable));
Volley wont cache anything, if cache control is not set in the response header.
Check the HttpHeaderParser class implementation in Volley.
Caching can be based on max-age or E-tag. Check your response header and identify anything set there. It will look something like this.
Cache-Control → public, max-age=300
Cache Header info
This is the exact way you want it to work.
Hit the url and get the image when its not available.
Load the image from the cache if available.