Android image caching

113,449

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:

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.)

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" />
Share:
113,449
d-man
Author by

d-man

Full stack Web Developer

Updated on February 08, 2020

Comments

  • d-man
    d-man over 4 years

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

  • d-man
    d-man over 14 years
    how can we write image on sd or phone memory?
  • Samuh
    Samuh over 14 years
    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.
  • CommonsWare
    CommonsWare over 14 years
    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.
  • Ljdawson
    Ljdawson over 14 years
    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?
  • Samuh
    Samuh over 14 years
    Speaking of efficiency, wouldn't it be efficient if instead of passing FileOutputStream we pass BufferedOutputStream?
  • Kevin Read
    Kevin Read over 13 years
    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.
  • Nanne
    Nanne over 13 years
    Am going to try this! Should the 'result' in the the perchance be "response"?
  • james
    james over 13 years
    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
  • Ljdawson
    Ljdawson over 13 years
    With an APK limit of 50mb now, caching to the SD card may be the only way for developers.
  • Adam
    Adam about 13 years
    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.
  • Tyler Collier
    Tyler Collier almost 13 years
    connection.getContent() always returns an InputStream for me, what am I doing wrong?
  • SHRISH M
    SHRISH M almost 13 years
    If I could now also set an expiration date on the content for the cache my life would be so much easier :)
  • Christopher Perry
    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
    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.getConten‌​t());
  • Jake Wilson
    Jake Wilson over 12 years
    What is cached? The InputStream? How do you test if the caching is working?
  • Keith
    Keith over 12 years
    Be sure to see Joe's answer below about the extra steps you need to take to get the cache working
  • Christopher Perry
    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
    esilver over 12 years
    Agreed - SoftReferences are reclaimed very quickly on the devices I have tested
  • Admin
    Admin over 12 years
    @edrowland: Awww man! I feel for you! ;) This post made my day!
  • esilver
    esilver over 12 years
    He's refactored his code since 2010; here's the root link: github.com/kaeppler/droid-fu
  • Charlie Collins
    Charlie Collins about 12 years
    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.
  • Gordon Glas
    Gordon Glas about 12 years
    You 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
    kaka almost 12 years
    Google have themselves confirmed that Dalvik's GC is very aggressive on collecting SoftReferences. They recommend using their LruCache instead.
  • shkschneider
    shkschneider over 11 years
    Use Object data = connection.getContent() or InputStream inputStream = connection.getInputStream()
  • Telémako
    Telémako over 11 years
    Here is the file
  • Felipe Lima
    Felipe Lima over 11 years
    That link still doesn't work. I wrote a similar library called Android-ImageManager github.com/felipecsl/Android-ImageManager
  • Admin
    Admin over 11 years
    I added this to a ListView and it doesn't seem to handle that very well. Is there some special implementation for ListViews?
  • Almer
    Almer over 11 years
    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);.
  • Weblance
    Weblance about 11 years
    Deprecated. Now see BitmapFun.
  • Geert Bellemans
    Geert Bellemans almost 11 years
    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...
  • Renetik
    Renetik almost 11 years
    USE singletons when you know how ! :)
  • sschuberth
    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
    Edwin Evans almost 10 years
    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!
  • Greyshack
    Greyshack about 9 years
    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.
  • Zubair Ahmed
    Zubair Ahmed about 9 years
    @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.
  • Greyshack
    Greyshack about 9 years
    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.
  • Zubair Ahmed
    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 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 developer.android.com/reference/android/graphics/…
  • Greyshack
    Greyshack about 9 years
    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.
  • Zubair Ahmed
    Zubair Ahmed about 9 years
    Yes When bounds are ok then it will return Bitmap other wise it will return null. Check the Usage I've defined above...
  • xyz
    xyz about 9 years
    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.
  • MR. Garcia
    MR. Garcia almost 9 years
    Upvote for that if(cache == null) which solved my problem! :)
  • DolDurma
    DolDurma about 8 years
    @ZubairAhmadKhan Your solution can be update single image into cache?
  • Zubair Ahmed
    Zubair Ahmed about 8 years
    @Mahdi.Pishguy Yes see my answer's Usage from activity part and a Note above it.
  • DolDurma
    DolDurma about 8 years
    @ZubairAhmadKhan Thanks. now how to renew image saved into cache? i dont see any solution about that
  • Zubair Ahmed
    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 in LruCache with that key and replace new url with that key
  • DolDurma
    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
    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
    Zubair Ahmed about 8 years
    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: square.github.io/picasso and Glide: futurestud.io/blog/glide-getting-started
  • DolDurma
    DolDurma about 8 years
    @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
    DolDurma about 8 years
  • iOSAndroidWindowsMobileAppsDev
    iOSAndroidWindowsMobileAppsDev about 8 years
    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?
  • Petro
    Petro about 8 years
    getImageFromWareHouse should be getImageFromWarehouse
  • Felix D.
    Felix D. over 7 years
    Google codesearch link is dead (again?), please update the link.
  • iOSAndroidWindowsMobileAppsDev
    iOSAndroidWindowsMobileAppsDev over 7 years
    is it possible to use lrucache in conjuction with httpresponsecache
  • TheRealChx101
    TheRealChx101 almost 5 years
    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
    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 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.
  • CoDe
    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
    TheRealChx101 almost 5 years
    @CoDe Visit this link for now, android.jlelse.eu/…
  • CoDe
    CoDe almost 5 years
    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 !!
  • Ali Akram
    Ali Akram over 4 years
    I need to understand why adapter.notifyDataSetChanged(); wont it automatically show image in imageview ?
  • Zubair Ahmed
    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
    Ali Akram over 4 years
    @ZubairAhmed so every time an image is loaded by download task notifyDataSetChanged() will be called. Right?
  • Zubair Ahmed
    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