144

How can I cache images after they are downloaded from web?

Ben Scheirman
  • 40,531
  • 21
  • 102
  • 137
d-man
  • 57,473
  • 85
  • 212
  • 296

18 Answers18

178

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.

Chris Lacy
  • 4,222
  • 3
  • 35
  • 33
edrowland
  • 1,789
  • 1
  • 10
  • 2
  • 1
    Wow, 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. – Kevin Read Oct 16 '10 at 20:44
  • Am going to try this! Should the 'result' in the the perchance be "response"? – Nanne Jan 05 '11 at 09:19
  • Doesn'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. – Adam Jun 27 '11 at 05:11
  • 11
    `connection.getContent()` always returns an InputStream for me, what am I doing wrong? – Tyler Collier Jul 07 '11 at 18:36
  • 3
    If I could now also set an expiration date on the content for the cache my life would be so much easier :) – Janusz Jul 12 '11 at 07:11
  • @Tyler, Cast the response to InputStream then use BitmapLoader to load the Bitmap like so: BitmapLoader.loadFromResource(inputStream); – Christopher Perry Jul 29 '11 at 18:32
  • 11
    @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());` – Stephen Fuhry Sep 30 '11 at 18:07
  • What is cached? The InputStream? How do you test if the caching is working? – Jake Wilson Oct 11 '11 at 21:54
  • 6
    Be sure to see Joe's answer below about the extra steps you need to take to get the cache working – Keith Nov 01 '11 at 18:22
  • @edrowland: Awww man! I feel for you! ;) This post made my day! –  Dec 05 '11 at 22:43
  • You 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. – Charlie Collins Jun 13 '12 at 20:10
  • Use `Object data = connection.getContent()` or `InputStream inputStream = connection.getInputStream()` – shkschneider Oct 15 '12 at 09:46
  • 1
    @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](http://developer.android.com/reference/java/net/ContentHandlerFactory.html) like in this [code snippet](https://gist.github.com/sschuberth/6987893). – sschuberth Oct 15 '13 at 07:36
  • Thank you. I made a global app image cache (at least I learned how to do it!) but read this before implementing SD card cache. – xyz Jun 01 '15 at 10:45
66

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:

http://libs-for-android.googlecode.com/svn/reference/com/google/android/filecache/FileResponseCache.html

(I'd post this in a comment, but I apparently don't have enough SO karma.)

Jason Whitehorn
  • 13,585
  • 9
  • 54
  • 68
Joe
  • 803
  • 7
  • 7
  • Here is the [file](http://libs-for-android.googlecode.com/svn/reference/com/google/android/filecache/FileResponseCache.html) – Telémako Nov 22 '12 at 16:02
  • 2
    When you use an `HttpResponseCache`, you might find the `HttpResponseCache.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, use `connection.addRequestProperty("Cache-Control", "max-stale=" + MAX_STALE_CACHE);`. – Almer Mar 18 '13 at 14:51
  • 1
    Google codesearch link is dead (again?), please update the link. – Felix D. Dec 08 '16 at 15:26
  • Also, 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 Jul 16 '19 at 16:58
28

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.

Samuh
  • 36,316
  • 26
  • 109
  • 116
  • how can we write image on sd or phone memory? – d-man Dec 22 '09 at 11:05
  • To 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. – Samuh Dec 22 '09 at 11:39
  • @d-man I'd suggest writing the to disk first and then obtaining a `Uri` path reference that you can pass to `ImageView` and other custom views. Because each time you `compress`, 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 through `If-None-Match` and `ETag` headers. – TheRealChx101 Jul 16 '19 at 17:03
  • @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. – CoDe Aug 24 '19 at 01:06
  • @CoDe Visit this link for now, https://android.jlelse.eu/reducing-your-networking-footprint-with-okhttp-etags-and-if-modified-since-b598b8dd81a1 – TheRealChx101 Aug 24 '19 at 22:19
  • Thanks 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 !! – CoDe Aug 25 '19 at 00:28
27

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

Zubair Ahmed
  • 2,857
  • 2
  • 27
  • 47
  • 3
    Outstanding 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! – Edwin Evans Sep 06 '14 at 18:36
  • Could 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. – Greyshack Apr 28 '15 at 22:12
  • @Greyshack. Basically `LruCache` has key-value pair and whenever you've get the image url `getImage()` will download the image from url. The Url of the image will be the `key` of `LruCache` and Bitmap will be the value and if you take a closer look at `DownloadImageTask` you can set `desiredWidth` and `desiredHeight` value the lesser width and height you set the lower image quality you will see. – Zubair Ahmed Apr 29 '15 at 04:00
  • inSampleSize = 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. – Greyshack Apr 29 '15 at 07:23
  • `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 increase `inSampleSize` and call the function again (like recursive function do). This is just because whenever you increase `inSampleSize`, it requests the decoder to subsample the original image, returning a smaller image to save memory. You can read about what `insampleSize` do from this link http://developer.android.com/reference/android/graphics/BitmapFactory.Options.html#inSampleSize – Zubair Ahmed Apr 29 '15 at 07:44
  • I 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. – Greyshack Apr 29 '15 at 17:35
  • Yes When bounds are ok then it will return Bitmap other wise it will return null. Check the Usage I've defined above... – Zubair Ahmed Apr 30 '15 at 04:12
  • 1
    Upvote for that `if(cache == null)` which solved my problem! :) – Koorosh Sep 10 '15 at 21:09
  • @ZubairAhmadKhan Your solution can be update single image into cache? – DolDurma Apr 25 '16 at 15:21
  • @Mahdi.Pishguy Yes see my answer's `Usage from activity` part and a `Note` above it. – Zubair Ahmed Apr 26 '16 at 04:31
  • @ZubairAhmadKhan Thanks. now how to renew image saved into cache? i dont see any solution about that – DolDurma Apr 26 '16 at 05:32
  • @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 in `LruCache` with that key and replace new url with that key – Zubair Ahmed Apr 26 '16 at 05:40
  • @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 – DolDurma Apr 26 '16 at 05:46
  • @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 Apr 26 '16 at 05:56
  • 1
    Also see my Edited answer at the end. I've mentioned about famous libraries used by most of developers now a days. Try those Picasso: http://square.github.io/picasso/ and Glide: https://futurestud.io/blog/glide-getting-started – Zubair Ahmed Apr 26 '16 at 05:59
  • @ZubairAhmadKhan is this right? images url saved as an `key` and content of images are `value`, for renew image into cache i must be for example add `milisecond` end of url such as `URL/google.jpg?timestamp=TIMESTAMP`, then image key as `image url` changed and after request again that downloaded again and cache into `LruCache`. ok? – DolDurma Apr 26 '16 at 06:01
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/110210/discussion-between-mahdi-pishguy-and-zubair-ahmad-khan). – DolDurma Apr 26 '16 at 06:48
  • Hi 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? – iOSAndroidWindowsMobileAppsDev Apr 26 '16 at 07:53
  • `getImageFromWareHouse` should be `getImageFromWarehouse` – Petro May 07 '16 at 01:21
  • I need to understand why `adapter.notifyDataSetChanged();` wont it automatically show image in `imageview` ? – Ali Akram Oct 03 '19 at 05:41
  • @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 :) – Zubair Ahmed Oct 07 '19 at 04:45
  • @ZubairAhmed so every time an image is loaded by download task `notifyDataSetChanged()` will be called. Right? – Ali Akram Oct 08 '19 at 08:02
  • @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 – Zubair Ahmed Oct 08 '19 at 11:20
18

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" />
Ljdawson
  • 12,091
  • 11
  • 45
  • 60
  • 11
    Why 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. – CommonsWare Dec 22 '09 at 16:33
  • 2
    Fair 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? – Ljdawson Dec 22 '09 at 17:32
  • Speaking of efficiency, wouldn't it be efficient if instead of passing FileOutputStream we pass BufferedOutputStream? – Samuh Dec 23 '09 at 04:18
  • 1
    i 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 – james Jan 28 '11 at 20:52
  • With an APK limit of 50mb now, caching to the SD card may be the only way for developers. – Ljdawson Jan 28 '11 at 21:24
  • @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//files/. When the user deletes the app, this directory will be cleaned out. – Christopher Perry Nov 09 '11 at 21:21
  • You can use [getExternalCacheDir](http://developer.android.com/reference/android/content/Context.html#getExternalCacheDir%28%29) 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. – Gordon Glas Jun 25 '12 at 18:08
13

I would consider using droidfu's image cache. It implements both an in-memory and disk-based image cache. You also get a WebImageView that takes advantage of the ImageCache library.

Here is the full description of droidfu and WebImageView: http://brainflush.wordpress.com/2009/11/23/droid-fu-part-2-webimageview-and-webgalleryadapter/

Kunu
  • 5,078
  • 6
  • 33
  • 61
esilver
  • 27,713
  • 23
  • 122
  • 168
  • He's refactored his code since 2010; here's the root link: https://github.com/kaeppler/droid-fu – esilver Apr 01 '12 at 22:33
  • 3
    That link still doesn't work. I wrote a similar library called Android-ImageManager https://github.com/felipecsl/Android-ImageManager – Felipe Lima Feb 12 '13 at 05:58
9

I've tried SoftReferences, they are too aggressively reclaimed in android that I felt there was no point using them

2cupsOfTech
  • 5,953
  • 4
  • 34
  • 57
  • 2
    Agreed - SoftReferences are reclaimed very quickly on the devices I have tested – esilver Nov 29 '11 at 07:04
  • 3
    Google have themselves confirmed that Dalvik's GC is very aggressive on collecting `SoftReference`s. They recommend using their `LruCache` instead. – kaka Sep 04 '12 at 21:08
9

As Thunder Rabbit suggested, ImageDownloader is the best one for the job. I also found a slight variation of the class at:

http://theandroidcoder.com/utilities/android-image-download-and-caching/

The main difference between the two is that the ImageDownloader uses the Android caching system, and the modified one uses internal and external storage as caching, keeping the cached images indefinitely or until the user removes it manually. The author also mentions Android 2.1 compatibility.

TheHippo
  • 61,720
  • 15
  • 75
  • 100
EZFrag
  • 317
  • 12
  • 29
7

There is a special entry on the official training section of Android about this: http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html

The section is quite new, it was not there when the question was asked.

The suggested solution is to use a LruCache. That class was introduced on Honeycomb, but it is also included on the compatibility library.

You can initialize a LruCache by setting the maximum number or entries and it will automatically sort them your you and clean them less used ones when you go over the limit. Other than that it is used as a normal Map.

The sample code from the official page:

private LruCache mMemoryCache;

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    // Get memory class of this device, exceeding this amount will throw an
    // OutOfMemory exception.
    final int memClass = ((ActivityManager) context.getSystemService(
            Context.ACTIVITY_SERVICE)).getMemoryClass();

    // Use 1/8th of the available memory for this memory cache.
    final int cacheSize = 1024 * 1024 * memClass / 8;

    mMemoryCache = new LruCache(cacheSize) {
        @Override
        protected int sizeOf(String key, Bitmap bitmap) {
            // The cache size will be measured in bytes rather than number of items.
            return bitmap.getByteCount();
        }
    };
    ...
}

public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
    if (getBitmapFromMemCache(key) == null) {
        mMemoryCache.put(key, bitmap);
    }
}

public Bitmap getBitmapFromMemCache(String key) {
    return mMemoryCache.get(key);
}

Previously SoftReferences were a good alternative, but not anymore, quoting from the official page:

Note: In the past, a popular memory cache implementation was a SoftReference or WeakReference bitmap cache, however this is not recommended. Starting from Android 2.3 (API Level 9) the garbage collector is more aggressive with collecting soft/weak references which makes them fairly ineffective. In addition, prior to Android 3.0 (API Level 11), the backing data of a bitmap was stored in native memory which is not released in a predictable manner, potentially causing an application to briefly exceed its memory limits and crash.

shalafi
  • 3,926
  • 2
  • 23
  • 27
7

This is a good catch by Joe. The code example above has two problems - one - the response object isn't an instance of Bitmap (when my URL references a jpg, like http:\website.com\image.jpg, its a

org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl$LimitedInputStream).

Second, as Joe points out, no caching occurs without a response cache being configured. Android developers are left to roll their own cache. Here's an example for doing so, but it only caches in memory, which really isn't the full solution.

http://codebycoffee.com/2010/06/29/using-responsecache-in-an-android-app/

The URLConnection caching API is described here:

http://download.oracle.com/javase/6/docs/technotes/guides/net/http-cache.html

I still think this is an OK solution to go this route - but you still have to write a cache. Sounds like fun, but I'd rather write features.

Peter Pascale
  • 1,538
  • 1
  • 15
  • 10
3

Consider using Universal Image Loader library by Sergey Tarasevich. It comes with:

  • Multithread image loading. It lets you can define the thread pool size
  • Image caching in memory, on device's file sytem and SD card.
  • Possibility to listen to loading progress and loading events

Universal Image Loader allows detailed cache management for downloaded images, with the following cache configurations:

  • UsingFreqLimitedMemoryCache: The least frequently used bitmap is deleted when the cache size limit is exceeded.
  • LRULimitedMemoryCache: The least recently used bitmap is deleted when the cache size limit is exceeded.
  • FIFOLimitedMemoryCache: The FIFO rule is used for deletion when the cache size limit is exceeded.
  • LargestLimitedMemoryCache: The largest bitmap is deleted when the cache size limit is exceeded.
  • LimitedAgeMemoryCache: The Cached object is deleted when its age exceeds defined value.
  • WeakMemoryCache: A memory cache with only weak references to bitmaps.

A simple usage example:

ImageView imageView = groupView.findViewById(R.id.imageView);
String imageUrl = "http://site.com/image.png"; 

ImageLoader imageLoader = ImageLoader.getInstance();
imageLoader.init(ImageLoaderConfiguration.createDefault(context));
imageLoader.displayImage(imageUrl, imageView);

This example uses the default UsingFreqLimitedMemoryCache.

Gunnar Karlsson
  • 28,350
  • 10
  • 68
  • 71
  • When 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... – Geert Bellemans Jul 04 '13 at 12:25
  • 1
    USE singletons when you know how ! :) – Renetik Aug 08 '13 at 11:53
3

What actually worked for me was setting ResponseCache on my Main class:

try {
   File httpCacheDir = new File(getApplicationContext().getCacheDir(), "http");
   long httpCacheSize = 10 * 1024 * 1024; // 10 MiB
   HttpResponseCache.install(httpCacheDir, httpCacheSize);
} catch (IOException e) { } 

and

connection.setUseCaches(true);

when downloading bitmap.

http://practicaldroid.blogspot.com/2013/01/utilizing-http-response-cache.html

ZamPrano
  • 63
  • 1
  • 1
  • 8
2

Google's libs-for-android has a nice libraries for managing image and file cache.

http://code.google.com/p/libs-for-android/

Taoufix
  • 372
  • 3
  • 8
1

I suggest IGNITION this is even better than Droid fu

https://github.com/kaeppler/ignition

https://github.com/kaeppler/ignition/wiki/Sample-applications

Jorgesys
  • 124,308
  • 23
  • 334
  • 268
1

I had been wrestling with this for some time; the answers using SoftReferences would lose their data too quickly. The answers that suggest instantiating a RequestCache were too messy, plus I could never find a full example.

But ImageDownloader.java works wonderfully for me. It uses a HashMap until the capacity is reached or until the purge timeout occurs, then things get moved to a SoftReference, thereby using the best of both worlds.

Thunder Rabbit
  • 5,405
  • 8
  • 44
  • 82
0

Even later answer, but I wrote an Android Image Manager that handles caching transparently (memory and disk). The code is on Github https://github.com/felipecsl/Android-ImageManager

Felipe Lima
  • 10,530
  • 4
  • 41
  • 39
  • 1
    I added this to a ListView and it doesn't seem to handle that very well. Is there some special implementation for ListViews? –  Feb 26 '13 at 22:13
0

Late answer but I think this library will help a lot with caching images : https://github.com/crypticminds/ColdStorage.

Simply annotate the ImageView with @LoadCache(R.id.id_of_my_image_view, "URL_to_downlaod_image_from) and it will take care of downloading the image and loading it into the image view. You can also specify a placeholder image and loading animation.

Detailed documentation of the annotation is present here :- https://github.com/crypticminds/ColdStorage/wiki/@LoadImage-annotation

0

Late answer, but I figured I should add a link to my site because I have written a tutorial how to make an image cache for android: http://squarewolf.nl/2010/11/android-image-cache/ Update: the page has been taken offline as the source was outdated. I join @elenasys in her advice to use Ignition.

So to all the people who stumble upon this question and haven't found a solution: hope you enjoy! =D

Thomas Vervest
  • 2,131
  • 1
  • 16
  • 13