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.
Related
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!!
Does anybody know if it is possible to clear the cached image from a single NetworkImageView using Googles Volley library?
I have an avatar image in a NetworkImageView and would like to show the new image once I have uploaded it to the server. At the moment if I do
profileImg.setImageUrl("myUrl", mImageLoader); I get the cached image.
Check this out :
1) Turning off cache :
If you want disable the cache for a particular url, you can use setShouldCache() method as below.
StringRequest stringReq = new StringRequest(....);
stringReq.setShouldCache(false);
2) Deleting cache for particular URL : Use remove() to delete cache of an URL.
yourRequestQueue.getCache().remove(url);
3) Deleting all the cache :
yourRequestQueue.getCache().clear(url);
Also, check out this link here.
Hope this helps.
So to belatedly answer my own question. I ended up taking a different approach to ensure that I always get the latest copy of an avatar image and it turned out to be very simple.
Instead of trying to clear out an individual image from the cache I use the following method.
int time = (int) (System.currentTimeMillis());//gets the current time in milliseconds
String myUrl = "www.mySite.com?timestamp=" + String.ValueOf(time);
profileImg.setImageUrl("myUrl", mImageLoader);
What this does is to append an imaginary parameter to the url I call to get the image with a current timestamp. This guarantees that I am always making a call to a unique url and so therefore am always getting the most up-to-date version of that image.
The beauty of this approach is that it is not limited to Volley and can be used however you choose to make network calls.
in the LruBitmapCache you should do diable put(url, bitmap) :
#Override
public void putBitmap(String url, Bitmap bitmap) {
// put(url, bitmap);
}
by this way every time you call the volley method image don't save to catch
To turn off the cache:
1
request.setShouldCache(false);
to remove the cache for a specific request:
1
queue.getCache().remove(url);
to clear all cache:
1
queue.getCache().clear();
to invalidate the cache: this will allow to display the cached data until the response is received. When the response is received, it will automatically override the cached data.
1
queue.getCache().invalidate(url, true);
for more details you can refer to the
url:http://androidresearch.wordpress.com/2014/02/01/android-volley-tutorial/
If you create the cache like this:
RequestQueue requests = Volley.newRequestQueue( context );
BitmapCache cache = new BitmapCache(cacheSize);
ImageLoader loader = new ImageLoader( requests, cache );
You can clear all like this:
public void clear () {
cache.evictAll();
requests.getCache().clear();
}
I have created volleySingleton Class. I load image from ImageLoader . I have a requirement of deleting the cache but not able do that. Used all these three method
1) Deleting cache for particular URL : Use remove() to delete cache of an URL.
VolleySingleton.getInstance().getRequestQueue().getCache().remove(ImageUrl);
2) Deleting all the cache :
VolleySingleton.getInstance().getRequestQueue().getCache().clear();
3)
VolleySingleton.getInstance().getRequestQueue().getCache().invalidate(ImageUrl,true);
I have tried all of them but cashe is not getting deleted
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.
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 am using following example to display internet images in my activity.
http://developer.android.com/resources/tutorials/views/hello-gridview.html
In custom image adapter I'm directly loading images from internet and assigning it to imageview.
Which shows images in gridview and every thing works fine but it is not efficient way.
When ever i scroll gridview it again and again loads images and thats why gridview scrolls very slow
Is there caching or some useful technique available to make it faster?
Create a global and static method which returns a Bitmap. This method will take parameters: context,imageUrl, and imageName.
in the method:
check if the file already exists in the cache. if it does, return the bitmap
if(new File(context.getCacheDir(), imageName).exists())
return BitmapFactory.decodeFile(new File(context.getCacheDir(), imageName).getPath());
otherwise you must load the image from the web, and save it to the cache:
image = BitmapFactory.decodeStream(HttpClient.fetchInputStream(imageUrl));
FileOutputStream fos = null;
try {
fos = new FileOutputStream(new File(context.getCacheDir(), imageName));
}
//this should never happen
catch(FileNotFoundException e) {
if(Constants.LOGGING)
Log.e(TAG, e.toString(), e);
}
//if the file couldn't be saved
if(!image.compress(Bitmap.CompressFormat.JPEG, 100, fos)) {
Log.e(TAG, "The image could not be saved: " + imageName + " - " + imageUrl);
image = BitmapFactory.decodeResource(context.getResources(), R.drawable.default_cached_image);
}
fos.flush();
fos.close();
return image;
preload a Vector<SoftReference<Bitmap>> object with all of the bitmaps using the method above in an AsyncTask class, and also another List holding a Map of imageUrls and imageNames(for later access when you need to reload an image), then set your GridView adapter.
i recommend using an array of SoftReferences to reduce the amount of memory used. if you have a huge array of bitmaps you're likely to run into memory problems.
so in your getView method, you may have something like(where icons is a Vector holding type SoftReference<Bitmap>:
myImageView.setImageBitmap(icons.get(position).get());
you would need to do a check:
if(icons.get(position).get() == null) {
myImageView.setImageBitmap(defaultBitmap);
new ReloadImageTask(context).execute(position);
}
in the ReloadImageTask AsyncTask class, simply call the global method created from above with the correct params, then notifyDataSetChanged in onPostExecute
some additional work may need to be done to ensure you don't start this AsyncTask when it is already running for a particular item
You will need to implement the caching yourself. Create a proxy class that will download the images. In the getView ask this class to download an image by passing a url. In the proxy class create a HashMap that will map a url to a Bitmap. If the key for the passed url doesn't exist, download the image and store it. Otherwise returned the stored bitmap converted to an imageView.
Of course you can't afford to store as many images as you like. You need to set a limit, for example 10 images, based on the image size you expect to have. When the limit is exceeded, you need to discard old images in the favor of new ones.
You could try DroidFu. My app uses the ImageCache. There's also some manner of web-based imageview or something of the sort in the library. See in particular WebImageView and WebGalleryAdapter: http://mttkay.github.com/droid-fu/index-all.html
Edited to add: The droid-fu project is deprecated in favor of Ignition. https://github.com/mttkay/ignition