Android image caching
Solution 1
And now the punchline: use the system cache.
URL url = new URL(strUrl);
URLConnection connection = url.openConnection();
connection.setUseCaches(true);
Object response = connection.getContent();
if (response instanceof Bitmap) {
Bitmap bitmap = (Bitmap)response;
}
Provides both memory and flash-rom cache, shared with the browser.
grr. I wish somebody had told ME that before i wrote my own cache manager.
Solution 2
Regarding the elegant connection.setUseCaches
solution above: sadly, it won't work without some additional effort. You will need to install a ResponseCache
using ResponseCache.setDefault
. Otherwise, HttpURLConnection
will silently ignore the setUseCaches(true)
bit.
See the comments at the top of FileResponseCache.java
for details:
(I'd post this in a comment, but I apparently don't have enough SO karma.)
Solution 3
Convert them into Bitmaps and then either store them in a Collection(HashMap,List etc.) or you can write them on the SDcard.
When storing them in application space using the first approach, you might want to wrap them around a java.lang.ref.SoftReference specifically if their numbers is large (so that they are garbage collected during crisis). This could ensue a Reload though.
HashMap<String,SoftReference<Bitmap>> imageCache =
new HashMap<String,SoftReference<Bitmap>>();
writing them on SDcard will not require a Reload; just a user-permission.
Solution 4
Use LruCache
to cache images efficiently. You can read about LruCache
from Android Developer site
I've used below solution for Images download and caching in android. You can follow steps below:
STEP 1:
make Class Named ImagesCache
. I've used Singleton object for this class
import android.graphics.Bitmap;
import android.support.v4.util.LruCache;
public class ImagesCache
{
private LruCache<String, Bitmap> imagesWarehouse;
private static ImagesCache cache;
public static ImagesCache getInstance()
{
if(cache == null)
{
cache = new ImagesCache();
}
return cache;
}
public void initializeCache()
{
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() /1024);
final int cacheSize = maxMemory / 8;
System.out.println("cache size = "+cacheSize);
imagesWarehouse = new LruCache<String, Bitmap>(cacheSize)
{
protected int sizeOf(String key, Bitmap value)
{
// The cache size will be measured in kilobytes rather than number of items.
int bitmapByteCount = value.getRowBytes() * value.getHeight();
return bitmapByteCount / 1024;
}
};
}
public void addImageToWarehouse(String key, Bitmap value)
{
if(imagesWarehouse != null && imagesWarehouse.get(key) == null)
{
imagesWarehouse.put(key, value);
}
}
public Bitmap getImageFromWarehouse(String key)
{
if(key != null)
{
return imagesWarehouse.get(key);
}
else
{
return null;
}
}
public void removeImageFromWarehouse(String key)
{
imagesWarehouse.remove(key);
}
public void clearCache()
{
if(imagesWarehouse != null)
{
imagesWarehouse.evictAll();
}
}
}
STEP 2:
make another class named DownloadImageTask which is used if bitmap is not available in cache it will download it from here:
public class DownloadImageTask extends AsyncTask<String, Void, Bitmap>
{
private int inSampleSize = 0;
private String imageUrl;
private BaseAdapter adapter;
private ImagesCache cache;
private int desiredWidth, desiredHeight;
private Bitmap image = null;
private ImageView ivImageView;
public DownloadImageTask(BaseAdapter adapter, int desiredWidth, int desiredHeight)
{
this.adapter = adapter;
this.cache = ImagesCache.getInstance();
this.desiredWidth = desiredWidth;
this.desiredHeight = desiredHeight;
}
public DownloadImageTask(ImagesCache cache, ImageView ivImageView, int desireWidth, int desireHeight)
{
this.cache = cache;
this.ivImageView = ivImageView;
this.desiredHeight = desireHeight;
this.desiredWidth = desireWidth;
}
@Override
protected Bitmap doInBackground(String... params)
{
imageUrl = params[0];
return getImage(imageUrl);
}
@Override
protected void onPostExecute(Bitmap result)
{
super.onPostExecute(result);
if(result != null)
{
cache.addImageToWarehouse(imageUrl, result);
if(ivImageView != null)
{
ivImageView.setImageBitmap(result);
}
else if(adapter != null)
{
adapter.notifyDataSetChanged();
}
}
}
private Bitmap getImage(String imageUrl)
{
if(cache.getImageFromWarehouse(imageUrl) == null)
{
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
options.inSampleSize = inSampleSize;
try
{
URL url = new URL(imageUrl);
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
InputStream stream = connection.getInputStream();
image = BitmapFactory.decodeStream(stream, null, options);
int imageWidth = options.outWidth;
int imageHeight = options.outHeight;
if(imageWidth > desiredWidth || imageHeight > desiredHeight)
{
System.out.println("imageWidth:"+imageWidth+", imageHeight:"+imageHeight);
inSampleSize = inSampleSize + 2;
getImage(imageUrl);
}
else
{
options.inJustDecodeBounds = false;
connection = (HttpURLConnection)url.openConnection();
stream = connection.getInputStream();
image = BitmapFactory.decodeStream(stream, null, options);
return image;
}
}
catch(Exception e)
{
Log.e("getImage", e.toString());
}
}
return image;
}
STEP 3: Usage from your Activity
or Adapter
Note: If you want to load image from url from Activity
Class. Use the second Constructor of DownloadImageTask
, but if you want to display image from Adapter
use first Constructor of DownloadImageTask
(for example you have a image in ListView
and you are setting image from 'Adapter')
USAGE FROM ACTIVITY:
ImageView imv = (ImageView) findViewById(R.id.imageView);
ImagesCache cache = ImagesCache.getInstance();//Singleton instance handled in ImagesCache class.
cache.initializeCache();
String img = "your_image_url_here";
Bitmap bm = cache.getImageFromWarehouse(img);
if(bm != null)
{
imv.setImageBitmap(bm);
}
else
{
imv.setImageBitmap(null);
DownloadImageTask imgTask = new DownloadImageTask(cache, imv, 300, 300);//Since you are using it from `Activity` call second Constructor.
imgTask.execute(img);
}
USAGE FROM ADAPTER:
ImageView imv = (ImageView) rowView.findViewById(R.id.imageView);
ImagesCache cache = ImagesCache.getInstance();
cache.initializeCache();
String img = "your_image_url_here";
Bitmap bm = cache.getImageFromWarehouse(img);
if(bm != null)
{
imv.setImageBitmap(bm);
}
else
{
imv.setImageBitmap(null);
DownloadImageTask imgTask = new DownloadImageTask(this, 300, 300);//Since you are using it from `Adapter` call first Constructor.
imgTask.execute(img);
}
Note:
cache.initializeCache()
you can use this statement in the very first Activity of your application. Once you've initialized the cache you would never need to initialized it every time if you are using ImagesCache
instance.
I am never good at explaining things but hope this will help the beginners that how to cache using LruCache
and its usage :)
EDIT:
Now a days there are very famous libraries known as Picasso
and Glide
which can be used to load images very efficiently in android app. Try this very simple and usefull library Picasso for android and Glide For Android. You do not need to worry about cache images.
Picasso allows for hassle-free image loading in your application—often in one line of code!
Glide, just like Picasso, can load and display images from many sources, while also taking care of caching and keeping a low memory impact when doing image manipulations. It has been used by official Google apps (like the app for Google I/O 2015) and is just as popular as Picasso. In this series, we're going to explore the differences and advantages of Glide over Picasso.
You can also visit blog for difference between Glide and Picasso
Solution 5
To download an image and save to the memory card you can do it like this.
//First create a new URL object
URL url = new URL("http://www.google.co.uk/logos/holiday09_2.gif")
//Next create a file, the example below will save to the SDCARD using JPEG format
File file = new File("/sdcard/example.jpg");
//Next create a Bitmap object and download the image to bitmap
Bitmap bitmap = BitmapFactory.decodeStream(url.openStream());
//Finally compress the bitmap, saving to the file previously created
bitmap.compress(CompressFormat.JPEG, 100, new FileOutputStream(file));
Don't forget to add the Internet permission to your manifest:
<uses-permission android:name="android.permission.INTERNET" />
Comments
-
d-man over 4 years
How can I cache images after they are downloaded from web?
-
d-man over 14 yearshow can we write image on sd or phone memory?
-
Samuh over 14 yearsTo save images on SD card: You can either commit the Image Streams read from the remote server to memory using normal File I/O operations or if you have converted your images into Bitmap objects you can use Bitmap.compress() method.
-
CommonsWare over 14 yearsWhy are you decoding the JPEG and then re-encoding it? You are better served downloading the URL to a byte array, then using that byte array to create your Bitmap and write out to a File. Every time you decode and re-encode a JPEG, the image quality gets worse.
-
Ljdawson over 14 yearsFair point, was more for speed then anything. Although, if saved as a byte array and the source file was not a JPEG wouldn't the file need to be converted anyways? "decodeByteArray" from the SDK Returns "The decoded bitmap, or null if the image data could not be decoded" so this makes me think its always decoding the image data so would this not need re-encoding again?
-
Samuh over 14 yearsSpeaking of efficiency, wouldn't it be efficient if instead of passing FileOutputStream we pass BufferedOutputStream?
-
Kevin Read over 13 yearsWow, this was an incredibly elegant way to do this, thanks a lot. It is in no way slower than my own simple cache manager, and now I don't need to do housekeeping on a SD card folder.
-
Nanne over 13 yearsAm going to try this! Should the 'result' in the the perchance be "response"?
-
james over 13 yearsi don't suggest caching images to your SD card. once the application is uninstalled, the images do not get removed, causing the sd card to be filled up with useless garbage. saving images to the application's cache directory is preferred IMO
-
Ljdawson over 13 yearsWith an APK limit of 50mb now, caching to the SD card may be the only way for developers.
-
Adam about 13 yearsDoesn't this mean that if the browser cache gets full your cache will either stop working or it'll get cleared? Is there any way to set the connection to use a cache instance for your own app? I can't find one in the documentation.
-
Tyler Collier almost 13 years
connection.getContent()
always returns an InputStream for me, what am I doing wrong? -
SHRISH M almost 13 yearsIf I could now also set an expiration date on the content for the cache my life would be so much easier :)
-
Christopher Perry almost 13 years@Tyler, Cast the response to InputStream then use BitmapLoader to load the Bitmap like so: BitmapLoader.loadFromResource(inputStream);
-
Stephen Fuhry almost 13 years@Scienceprodigy no idea what that BitmapLoader is, certainly isn't in any standard android library I know of, but it at least led me in the right direction.
Bitmap response = BitmapFactory.decodeStream((InputStream)connection.getContent());
-
Jake Wilson over 12 yearsWhat is cached? The InputStream? How do you test if the caching is working?
-
Keith over 12 yearsBe sure to see Joe's answer below about the extra steps you need to take to get the cache working
-
Christopher Perry over 12 years@binnyb, That's not true if you store your data in the correct directory, on Android 2.2+ that is. You should be using Android/data/<your package name>/files/. When the user deletes the app, this directory will be cleaned out.
-
esilver over 12 yearsAgreed - SoftReferences are reclaimed very quickly on the devices I have tested
-
Admin over 12 years@edrowland: Awww man! I feel for you! ;) This post made my day!
-
esilver over 12 yearsHe's refactored his code since 2010; here's the root link: github.com/kaeppler/droid-fu
-
Charlie Collins about 12 yearsYou should also set timeouts on the connection if you're going to use URLConnection like that. If you're resource isn't available, for whatever reason, and you don't set timeouts, you're gonna have a bad time.
-
Gordon Glas about 12 yearsYou can use getExternalCacheDir to get a path on the SD card that will be cleaned up when the user deletes the app. But like docs say, it doesn't auto-clean that folder when a size limit is reached, while getCacheDir handles that for you. Don't forget getExternalCacheDir requires WRITE_EXTERNAL_STORAGE permission.
-
kaka almost 12 yearsGoogle have themselves confirmed that Dalvik's GC is very aggressive on collecting
SoftReference
s. They recommend using theirLruCache
instead. -
shkschneider over 11 yearsUse
Object data = connection.getContent()
orInputStream inputStream = connection.getInputStream()
-
Telémako over 11 yearsHere is the file
-
Felipe Lima over 11 yearsThat link still doesn't work. I wrote a similar library called Android-ImageManager github.com/felipecsl/Android-ImageManager
-
Admin over 11 yearsI added this to a ListView and it doesn't seem to handle that very well. Is there some special implementation for ListViews?
-
Almer over 11 yearsWhen you use an
HttpResponseCache
, you might find theHttpResponseCache.getHitCount()
returning 0. I'm not sure but I think it's because the webserver you're requesting doesn't use caching headers in that case. To make caching work anyway, useconnection.addRequestProperty("Cache-Control", "max-stale=" + MAX_STALE_CACHE);
. -
Weblance about 11 yearsDeprecated. Now see BitmapFun.
-
Geert Bellemans almost 11 yearsWhen used intensively, Universal Image Loader will cause a lot of memory leaks. I suspect this happens because it uses singletons in the code (see 'getInstance()' in the example). After loading a lot of images and then rotating my screen a couple of times, my app crashed all the time because OutOfMemoryErrors in UIL. It's a great library but it's a well known fact thet you should NEVER use singletons, especially not in Android...
-
Renetik almost 11 yearsUSE singletons when you know how ! :)
-
sschuberth over 10 years@TylerCollier
connection.getContent()
is not returning a bitmap because none of the default content handlers in URLConnection handles image types. You need to set a custom ContentHandlerFactory like in this code snippet. -
Edwin Evans almost 10 yearsOutstanding answer and explanation! I think this is the best solution since it works when offline and uses Android LruCache. I found edrowland's solution did not work in airplane mode even with Joe's addition which required more effort to integrate. Btw, it seems like Android or network provides a significant amount of caching even if you do nothing extra. (One minor nit: for sample usage getImageFromWareHouse, the 'H' should be lowercase to match.) Thanks!
-
Greyshack about 9 yearsCould you explain the getImage() method, in particular what it does to the image size and how it happens. I do not understand for example why you call the function inside itself again and how it works.
-
Zubair Ahmed about 9 years@Greyshack. Basically
LruCache
has key-value pair and whenever you've get the image urlgetImage()
will download the image from url. The Url of the image will be thekey
ofLruCache
and Bitmap will be the value and if you take a closer look atDownloadImageTask
you can setdesiredWidth
anddesiredHeight
value the lesser width and height you set the lower image quality you will see. -
Greyshack about 9 yearsinSampleSize = inSampleSize + 2; getImage(imageUrl); This part I'm interested in. Basically you check the bounds of the image first, and if they are bigger than we want, you call the function inside again. What does this do? The function returns Bitmap and you do nothing with it.
-
Zubair Ahmed about 9 years
Basically you check the bounds of the image first, and if they are bigger than we want, you call the function inside again
Yes because if image is not upto the size we want, then increaseinSampleSize
and call the function again (like recursive function do). This is just because whenever you increaseinSampleSize
, it requests the decoder to subsample the original image, returning a smaller image to save memory. You can read about whatinsampleSize
do from this link developer.android.com/reference/android/graphics/… -
Greyshack about 9 yearsI get all that, I just dont see the recursive calling here. Because you call it inside again and lets say this time the bounds are ok. It downloafs image returns it to the first called function which does nothing with this returned value and returns basically a null value. But it somehow works and I just cant see it.
-
Zubair Ahmed about 9 yearsYes When bounds are ok then it will return Bitmap other wise it will return null. Check the Usage I've defined above...
-
xyz about 9 yearsThank you. I made a global app image cache (at least I learned how to do it!) but read this before implementing SD card cache.
-
MR. Garcia almost 9 yearsUpvote for that
if(cache == null)
which solved my problem! :) -
DolDurma about 8 years@ZubairAhmadKhan Your solution can be update single image into cache?
-
Zubair Ahmed about 8 years@Mahdi.Pishguy Yes see my answer's
Usage from activity
part and aNote
above it. -
DolDurma about 8 years@ZubairAhmadKhan Thanks. now how to renew image saved into cache? i dont see any solution about that
-
Zubair Ahmed about 8 years@Mahdi.Pishguy This could be possible. If you go through my solution again you will see
LruCache
has key-value pair and every image url is set as a key into the cache and its downloaded bitmap as value, so If you want to renew some url, you should remember that url that you want to renew and then just search inLruCache
with that key and replace new url with that key -
DolDurma about 8 years@ZubairAhmadKhan thanks. can you explain more than about it? can you paste simple sample to your topic? images url are single and i dont know whats your mean about key-value
-
Zubair Ahmed about 8 years@Mahdi.Pishguy Please review
ImagesCache
class above. It contains functions you can add more functions which you want at run time from this cache class. -
Zubair Ahmed about 8 yearsAlso see my Edited answer at the end. I've mentioned about famous libraries used by most of developers now a days. Try those Picasso: square.github.io/picasso and Glide: futurestud.io/blog/glide-getting-started
-
DolDurma about 8 years@ZubairAhmadKhan is this right? images url saved as an
key
and content of images arevalue
, for renew image into cache i must be for example addmilisecond
end of url such asURL/google.jpg?timestamp=TIMESTAMP
, then image key asimage url
changed and after request again that downloaded again and cache intoLruCache
. ok? -
DolDurma about 8 yearsLet us continue this discussion in chat.
-
iOSAndroidWindowsMobileAppsDev about 8 yearsHi I tried this and it works superb but I just lost 200M.B. of memory on my android and I have no control over that loss. Uninstalling the app or clearing cache does not help. I think it is best to cache images to disk since the end-user will hae more control of their memory. In the meantime, how do I restore the space I used up?
-
Petro about 8 years
getImageFromWareHouse
should begetImageFromWarehouse
-
Felix D. over 7 yearsGoogle codesearch link is dead (again?), please update the link.
-
iOSAndroidWindowsMobileAppsDev over 7 yearsis it possible to use lrucache in conjuction with httpresponsecache
-
TheRealChx101 almost 5 yearsAlso, I'm not sure whether or not this behavior is now fixed. For some reason returning 304 from the server would hang HUC when using
.getContent()
method because 304 responses don't have an associated response body by RFC standard. -
TheRealChx101 almost 5 years@d-man I'd suggest writing the to disk first and then obtaining a
Uri
path reference that you can pass toImageView
and other custom views. Because each time youcompress
, you'll be losing quality. Of course this is true for lossy algorithms only. This method would also allow you to even store a hash of the file and use it next time you request for the file from the server throughIf-None-Match
andETag
headers. -
CoDe almost 5 years@TheRealChx101 could you please help to understand what you mean next time you request for the file from the server through If-None-Match and ETag headers, I'm basically looking for solution approach where image should remain to use form local cache for defined period OR if this can not be achieved then whenever content for URL get change, it should reflect in application with the latest one and cached it.
-
TheRealChx101 almost 5 years@CoDe Visit this link for now, android.jlelse.eu/…
-
CoDe almost 5 yearsThanks and this is best article I found to understand this concept. I'm just going through with Glide and try to edit If-None-Match(eTag)/ If-Modified-Since(Date) manually using charles and it is working. Working with glide we can update header information but any idea around if these eTag and modified-date I have to store manually and then push in next header request or somewhere it is managed by Glide it self so I can directly read !!
-
Ali Akram over 4 yearsI need to understand why
adapter.notifyDataSetChanged();
wont it automatically show image inimageview
? -
Zubair Ahmed over 4 years@AliAkram DownloadImageTask has two constructors and in case of adapter we are not passing ImageView instead we're passing adapter object like if you see above
new DownloadImageTask(this, 300, 300)
and we have to notify adapter. Please read above from Step 3 I've already explained this above :) -
Ali Akram over 4 years@ZubairAhmed so every time an image is loaded by download task
notifyDataSetChanged()
will be called. Right? -
Zubair Ahmed over 4 years@AliAkram Yes but only when in Adapter's getView() method there is no image in cache It will download from DownloadImageTask, add into LruCache and notify adapter otherwise it will pick directly from cache. You can see if - else check in adapter's getView() method