How to lazy load images in ListView in Android

568,642

Solution 1

Here's what I created to hold the images that my app is currently displaying. Please note that the "Log" object in use here is my custom wrapper around the final Log class inside Android.

package com.wilson.android.library;

/*
 Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License.  You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
*/
import java.io.IOException;

public class DrawableManager {
    private final Map<String, Drawable> drawableMap;

    public DrawableManager() {
        drawableMap = new HashMap<String, Drawable>();
    }

    public Drawable fetchDrawable(String urlString) {
        if (drawableMap.containsKey(urlString)) {
            return drawableMap.get(urlString);
        }

        Log.d(this.getClass().getSimpleName(), "image url:" + urlString);
        try {
            InputStream is = fetch(urlString);
            Drawable drawable = Drawable.createFromStream(is, "src");


            if (drawable != null) {
                drawableMap.put(urlString, drawable);
                Log.d(this.getClass().getSimpleName(), "got a thumbnail drawable: " + drawable.getBounds() + ", "
                        + drawable.getIntrinsicHeight() + "," + drawable.getIntrinsicWidth() + ", "
                        + drawable.getMinimumHeight() + "," + drawable.getMinimumWidth());
            } else {
              Log.w(this.getClass().getSimpleName(), "could not get thumbnail");
            }

            return drawable;
        } catch (MalformedURLException e) {
            Log.e(this.getClass().getSimpleName(), "fetchDrawable failed", e);
            return null;
        } catch (IOException e) {
            Log.e(this.getClass().getSimpleName(), "fetchDrawable failed", e);
            return null;
        }
    }

    public void fetchDrawableOnThread(final String urlString, final ImageView imageView) {
        if (drawableMap.containsKey(urlString)) {
            imageView.setImageDrawable(drawableMap.get(urlString));
        }

        final Handler handler = new Handler(Looper.getMainLooper()) {
            @Override
            public void handleMessage(Message message) {
                imageView.setImageDrawable((Drawable) message.obj);
            }
        };

        Thread thread = new Thread() {
            @Override
            public void run() {
                //TODO : set imageView to a "pending" image
                Drawable drawable = fetchDrawable(urlString);
                Message message = handler.obtainMessage(1, drawable);
                handler.sendMessage(message);
            }
        };
        thread.start();
    }

    private InputStream fetch(String urlString) throws MalformedURLException, IOException {
        DefaultHttpClient httpClient = new DefaultHttpClient();
        HttpGet request = new HttpGet(urlString);
        HttpResponse response = httpClient.execute(request);
        return response.getEntity().getContent();
    }
}

Solution 2

I made a simple demo of a lazy list (located at GitHub) with images.

Basic Usage

ImageLoader imageLoader=new ImageLoader(context); ...
imageLoader.DisplayImage(url, imageView); 

Don't forget to add the following permissions to your AndroidManifest.xml:

 <uses-permission android:name="android.permission.INTERNET"/>
 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> Please

create only one instance of ImageLoader and reuse it all around your application. This way image caching will be much more efficient.

It may be helpful to somebody. It downloads images in the background thread. Images are being cached on an SD card and in memory. The cache implementation is very simple and is just enough for the demo. I decode images with inSampleSize to reduce memory consumption. I also try to handle recycled views correctly.

Alt text

Solution 3

I recommend open source instrument Universal Image Loader. It is originally based on Fedor Vlasov's project LazyList and has been vastly improved since then.

  • Multithread image loading
  • Possibility of wide tuning ImageLoader's configuration (thread executors, downloader, decoder, memory and disc cache, display image options, and others)
  • Possibility of image caching in memory and/or on the device's file system (or SD card)
  • Possibility to "listen" loading process
  • Possibility to customize every display image call with separated options
  • Widget support
  • Android 2.0+ support

Solution 4

Multithreading For Performance, a tutorial by Gilles Debunne.

This is from the Android Developers Blog. The suggested code uses:

  • AsyncTasks.
  • A hard, limited size, FIFO cache.
  • A soft, easily garbage collect-ed cache.
  • A placeholder Drawable while you download.

enter image description here

Solution 5

Update: Note that this answer is pretty ineffective now. The Garbage Collector acts aggressively on SoftReference and WeakReference, so this code is NOT suitable for new apps. (Instead, try libraries like Universal Image Loader suggested in other answers.)

Thanks to James for the code, and Bao-Long for the suggestion of using SoftReference. I implemented the SoftReference changes on James' code. Unfortunately, SoftReferences caused my images to be garbage collected too quickly. In my case, it was fine without the SoftReference stuff, because my list size is limited and my images are small.

There's a discussion from a year ago regarding the SoftReferences on google groups: link to thread. As a solution to the too-early garbage collection, they suggest the possibility of manually setting the VM heap size using dalvik.system.VMRuntime.setMinimumHeapSize(), which is not very attractive to me.

public DrawableManager() {
    drawableMap = new HashMap<String, SoftReference<Drawable>>();
}

public Drawable fetchDrawable(String urlString) {
    SoftReference<Drawable> drawableRef = drawableMap.get(urlString);
    if (drawableRef != null) {
        Drawable drawable = drawableRef.get();
        if (drawable != null)
            return drawable;
        // Reference has expired so remove the key from drawableMap
        drawableMap.remove(urlString);
    }

    if (Constants.LOGGING) Log.d(this.getClass().getSimpleName(), "image url:" + urlString);
    try {
        InputStream is = fetch(urlString);
        Drawable drawable = Drawable.createFromStream(is, "src");
        drawableRef = new SoftReference<Drawable>(drawable);
        drawableMap.put(urlString, drawableRef);
        if (Constants.LOGGING) Log.d(this.getClass().getSimpleName(), "got a thumbnail drawable: " + drawable.getBounds() + ", "
                + drawable.getIntrinsicHeight() + "," + drawable.getIntrinsicWidth() + ", "
                + drawable.getMinimumHeight() + "," + drawable.getMinimumWidth());
        return drawableRef.get();
    } catch (MalformedURLException e) {
        if (Constants.LOGGING) Log.e(this.getClass().getSimpleName(), "fetchDrawable failed", e);
        return null;
    } catch (IOException e) {
        if (Constants.LOGGING) Log.e(this.getClass().getSimpleName(), "fetchDrawable failed", e);
        return null;
    }
}

public void fetchDrawableOnThread(final String urlString, final ImageView imageView) {
    SoftReference<Drawable> drawableRef = drawableMap.get(urlString);
    if (drawableRef != null) {
        Drawable drawable = drawableRef.get();
        if (drawable != null) {
            imageView.setImageDrawable(drawableRef.get());
            return;
        }
        // Reference has expired so remove the key from drawableMap
        drawableMap.remove(urlString);
    }

    final Handler handler = new Handler() {
        @Override
        public void handleMessage(Message message) {
            imageView.setImageDrawable((Drawable) message.obj);
        }
    };

    Thread thread = new Thread() {
        @Override
        public void run() {
            //TODO : set imageView to a "pending" image
            Drawable drawable = fetchDrawable(urlString);
            Message message = handler.obtainMessage(1, drawable);
            handler.sendMessage(message);
        }
    };
    thread.start();
}
Share:
568,642
Vaerenberg
Author by

Vaerenberg

Developing and consulting for iOS and some Android since 2008. Worked on multiple top-selling and featured apps currently available on the App Store.

Updated on May 03, 2022

Comments

  • Vaerenberg
    Vaerenberg about 2 years

    I am using a ListView to display some images and captions associated with those images. I am getting the images from the Internet. Is there a way to lazy load images so while the text displays, the UI is not blocked and images are displayed as they are downloaded?

    The total number of images is not fixed.

    • Pascal Dimassimo
      Pascal Dimassimo over 12 years
      You can use GreenDroid's AsyncImageView. Just call setUrl.
    • borisstr
      borisstr about 12 years
      I have used it. It is a wonderful implementation. Bad news that AsyncImageView is a part of a large GreenDroid project, which make your application larger even in the case all you need is AsyncImageView. Also, seems, GreenDroid project is not updated since 2011.
    • Ritesh Kumar Dubey
      Ritesh Kumar Dubey over 11 years
      You can even give a try to this library : Android-http-image-manager which in my opinion is the best for asynchronous loading of images.
    • Anuj Sharma
      Anuj Sharma over 10 years
      Just use Picasso, it will do all itself. 'Picasso.with(yourContext).load(img src/path/drawable here).into(imageView i.e your target);' Thats it!
    • krunal patel
      krunal patel over 9 years
      try using :github.com/nostra13/Android-Universal-Image-Loader , this library is very fast and efficient for lazy loading and image caching
    • Devendra Kulkarni
      Devendra Kulkarni over 8 years
      Use Glide image loading library.
  • James A Wilson
    James A Wilson about 15 years
    using a thread for each image is the approach I use as well. If you separate your model from your view you can persist the model outside the Activity (like in your 'application' class) to keep them cached. Beware of running out of resources if you have many images.
  • Vaerenberg
    Vaerenberg about 15 years
    can you please elaborate. I am new to android development. Thanks for the tips though
  • Fedor
    Fedor almost 14 years
    Starting a new thread for each image is not an effective solution. You can end up with a lot of threads in memory and freezing UI.
  • jasonhudgins
    jasonhudgins almost 14 years
    Fedor, agreed, I usually use a queue and a thread pool, that's the best way to go imo.
  • AZ_
    AZ_ over 13 years
    you can create generations like hard-generation and softgeneration. you can fix a time clear cache will clear all images that were not accessed in 3 sec.. you can have a look at google shelves project
  • AZ_
    AZ_ over 13 years
    I think you should use SoftReferences so that your program will never cause OutOfMemoryException. As GC can clear softreferences when heap size is increasing... you can manage your own generation like after some seconds you can put your images to that list and before loading you should check that if image exists then don't download it again rather collect it from that list and also putting it back to your softref list and after sometime you can purge your hardlist :)
  • AZ_
    AZ_ over 13 years
    Google Shelves project is an excellent example look how they did code.google.com/p/shelves
  • Thomas Ahle
    Thomas Ahle about 13 years
    It works fine in 2.1 as well. Just don't use AndroidHttpClient.
  • Adinia
    Adinia about 13 years
    @thomas-ahle Thank you, I saw AndroidHttpClient was giving errors in 2.1, as it's implemented from 2.2, but didn't really tried to find something else to replace it.
  • Thomas Ahle
    Thomas Ahle about 13 years
    @Adina You are right, I forgot. However there is nothing in the recipe that can't just as well be done with a normal HttpClient.
  • Karussell
    Karussell about 13 years
    Don't you miss a return when drawableMap contains the image ... without starting the fetching-thread?
  • satur9nine
    satur9nine over 12 years
    This code has several problems. Firstly you should cache Drawables, that will cause a memory leak: stackoverflow.com/questions/7648740/… . Secondly the cache itself is never cleared so it will grow forever, that's another memory leak.
  • Noman
    Noman over 12 years
    @James A Wilson... where do i connect it with listview to show images as thumbnails?? m a newbie android dev... if someone could tell me plz
  • Mullins
    Mullins over 12 years
    Works great! BTW there's a typo in the class name.
  • Michael Reed
    Michael Reed over 12 years
    In case it saves someone else the time: import java.util.Map; import java.util.HashMap; import java.util.LinkedList; import java.util.Collections; import java.util.WeakHashMap; import java.lang.ref.SoftReference; import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; import android.graphics.drawable.Drawable; import android.widget.ImageView; import android.os.Handler; import android.os.Message; import java.io.InputStream; import java.net.MalformedURLException; import java.io.IOException; import java.net.URL; import java.net.URLConnection;
  • Sam
    Sam about 12 years
    ImageDownloader class not get complied: see the solution below code.google.com/p/parleys-android-nextgen/issues/detail?id=1
  • Juan Hernandez
    Juan Hernandez about 12 years
    Thanks very much, this is a nice implementation. I also put a different placeholder for when the drawable is being loaded so the user can get some feedback.
  • Juan Hernandez
    Juan Hernandez about 12 years
    Also I think is better to use a LIFO queue in the executorService (mThreadPool) instead of the default FIFO so last images requested (which probably are the visible ones) are loaded first. See stackoverflow.com/questions/4620061/how-to-create-lifo-execu‌​tor
  • Asaf Pinhassi
    Asaf Pinhassi about 12 years
    To implement the above with LIFO see stackoverflow.com/questions/6107609/… "Create a LifoBlockingDeque class"
  • SilithCrowe
    SilithCrowe about 12 years
    @MichaelReed, in case you're an Eclipse user, I recommend using Ctrl-Shift-O (that's the letter O, not the number 0). It automates the process of adding imports and organizes them for you. If you're on a Mac, use Command-Shift-O instead.
  • Atma
    Atma about 12 years
    this causes images to get the wrong image sources in a listview. For example I have icon 1 , 2, and 3. My list shows icon 1 and two 3 icons when using this drawable manager class. Does anyone know how this is fixed from this exact code above?
  • AlexAndro
    AlexAndro almost 12 years
    I have used this class to download my list of images from URL and then I have used the code below to clear the cache and everything seems to work great! clear cache - here
  • Michael Reed
    Michael Reed over 11 years
    @SilithCrowe Thanks for the tip- I am a Mac user. Appreciate running across this info while circling back around to the project I was working on when I ran across this in the first place.
  • praveenb
    praveenb over 11 years
    this worked perfectly. Now im saved from SkImageDecoder::Factory returned null error. which is painstaking from long time... Thank you somuch...@Pinhassi
  • njzk2
    njzk2 over 11 years
    only observation : you are starting an unknown number of threads. I would recommend using an Executor with a fixed number of threads, or AsyncTask (which are using a single Executor too)
  • slf
    slf over 11 years
    Why not just pass "this" to your logger wrapper? Then you don't have to call getClass().getSimpleName() all over the place
  • Asaf Pinhassi
    Asaf Pinhassi over 11 years
    The solution doesn't use asyc tast because the thread pool allows to download nultipile images simultaneously
  • mkuech
    mkuech over 11 years
    I'm sorry, I only pointed to a single class for the Google IO app (and I'm too late to edit). You should really study all their image loading and caching utility classes that you can find in the same package as the cache class.
  • vokilam
    vokilam about 11 years
    developer.android.com/reference/java/lang/ref/… SoftReference doc has a note about caching, see "Avoid Soft References for Caching" section. Most applications should use an android.util.LruCache instead of soft references.
  • Nicolas Jafelle
    Nicolas Jafelle about 11 years
    Actually Novoda is a great library but...sometimes you don't need a huge library and only a simple approach of the solution. That is why LazyList in Github is so good, if your apps only shows an image in a listView and is not the main feature of your app, just another activity I would prefer to use something lightier. Otherwise if you know that you will have to use often and is part of the core, try Novoda.
  • Gautam
    Gautam about 11 years
    Would anyone recommend grabbing the DiskLruCache, Image*.java files from the iosched app's util folder to help with handling image loading/caching for list view? I mean it's definitely worth following the online Developer guides on the subject but these classes (from iosched) go a little further with the pattern.
  • Muhammad Babar
    Muhammad Babar almost 11 years
    haven't any one heard about LRU Cache developer.android.com/training/displaying-bitmaps/…
  • j2emanue
    j2emanue almost 11 years
    I admire your code but now in the new Android O/S there is 'aggressive ' garbage collecting. Holding a weak reference does not make any sense to me.
  • TalkLittle
    TalkLittle almost 11 years
    @j2emanue You are right, as I tried to indicate at the top of my answer, SoftReferences are garbage collected too quickly. I'll try to edit this answer to make that even clearer.
  • CoDe
    CoDe over 10 years
    @Michael Reed: can use ctr+shift+O to perform auto import.
  • masha
    masha over 10 years
    Hi, I've had a look at the code examples but i'm having issues using ImageFetcher with an ArrayAdapter, would you mind looking at my question? stackoverflow.com/questions/21089147/… Thanks =]
  • Muhammad Ahmed AbuTalib
    Muhammad Ahmed AbuTalib almost 10 years
    I have heard at several places , that Google does not recommend soft references because the android kernel is very eager to collect these references compared to the earlier versions of the system .
  • Alexander Sidikov Pfeif
    Alexander Sidikov Pfeif over 9 years
    I think this is the best solution - the other answers are very old - volley is realy fast and combined with jake warthons disklrucache its a perfekt solution - i tried a lot of others but not one is stable and fast as volley
  • The Original Android
    The Original Android almost 9 years
    I like getting the bitmap in a different thread, of course. But the only issue I have with having this code in getView() is there will be many threads running for several images. And getView may try to load many or several images at one time.
  • buczek
    buczek almost 8 years
    In addition to your description, please include some code to further improve your answer.
  • Selim Raza
    Selim Raza about 7 years
    Nice Work to me ,But Need a Jar file to include in your project. You can download that JAR file from here AQuery androidAQuery = new AQuery(this); link Is:code.google.com/archive/p/android-query/downloads
  • lalit vasan
    lalit vasan almost 7 years
    This tutorial may help you more for PICASOO :- androidtutorialshub.com/… and GLIDE :- androidtutorialshub.com/…
  • anhtuannd
    anhtuannd over 6 years
    Sure, Picasso stores full image size for cache, while Glide stores optimized images only.
  • LordRaydenMK
    LordRaydenMK almost 6 years
    Picasso is a library developed by Square
  • Priyanka Singh
    Priyanka Singh almost 4 years
  • Priyanka Singh
    Priyanka Singh almost 4 years