2073

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.

Vivek Mishra
  • 5,669
  • 9
  • 46
  • 84
lostInTransit
  • 70,519
  • 61
  • 198
  • 274
  • 6
    You can even give a try to this library : [Android-http-image-manager](https://github.com/zonghai-li/android-http-image-manager) which in my opinion is the best for asynchronous loading of images. – Ritesh Kumar Dubey Nov 09 '12 at 11:43
  • 13
    You can use [GreenDroid's AsyncImageView](http://greendroid.cyrilmottier.com/reference/greendroid/widget/AsyncImageView.html). Just call `setUrl`. – Pascal Dimassimo Dec 09 '11 at 15:11
  • 8
    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. – borisstr Apr 15 '12 at 11:06
  • 36
    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! – Anuj Sharma Jan 23 '14 at 06:40
  • 10
    try using :https://github.com/nostra13/Android-Universal-Image-Loader , this library is very fast and efficient for lazy loading and image caching – krunal patel Aug 16 '14 at 05:44
  • 2
    Use Glide image loading library. – Devendra Kulkarni Dec 22 '15 at 14:40

41 Answers41

1149

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();
    }
}
gregrobinz
  • 23
  • 5
James A Wilson
  • 14,611
  • 5
  • 29
  • 31
  • 109
    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_ Jan 18 '11 at 08:08
  • 41
    Google Shelves project is an excellent example look how they did http://code.google.com/p/shelves/ – AZ_ Jan 18 '11 at 08:09
  • 14
    Don't you miss a return when drawableMap contains the image ... without starting the fetching-thread? – Karussell Mar 29 '11 at 22:06
  • 7
    This code has several problems. Firstly you should cache Drawables, that will cause a memory leak: http://stackoverflow.com/questions/7648740/consequences-of-drawable-setcallbacknull . Secondly the cache itself is never cleared so it will grow forever, that's another memory leak. – satur9nine Nov 15 '11 at 05:35
  • 3
    @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 – Noman Nov 30 '11 at 08:31
  • 3
    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? – Atma Mar 26 '12 at 22:26
  • 3
    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](http://stackoverflow.com/questions/6898090/how-to-clear-cache-android) – AlexAndro Jun 17 '12 at 10:59
  • 4
    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) – njzk2 Dec 11 '12 at 08:45
  • 3
    Why not just pass "this" to your logger wrapper? Then you don't have to call getClass().getSimpleName() all over the place – slf Dec 11 '12 at 14:51
  • 12
    haven't any one heard about `LRU Cache` http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html – Muhammad Babar May 28 '13 at 07:26
  • 1
    Can you help? https://stackoverflow.com/questions/62624070/how-to-priorly-get-future-glide-image-size-which-will-be-stored-in-cache-in-andr – Priyanka Singh Jun 28 '20 at 17:04
1051

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

Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140
Fedor
  • 43,261
  • 10
  • 79
  • 89
564

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

Rahul
  • 3,293
  • 2
  • 31
  • 43
nostra13
  • 12,377
  • 3
  • 33
  • 43
166

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

Has AlTaiar
  • 4,052
  • 2
  • 36
  • 37
Thomas Ahle
  • 30,774
  • 21
  • 92
  • 114
  • 11
    It works fine in 2.1 as well. Just don't use AndroidHttpClient. – Thomas Ahle Feb 05 '11 at 02:22
  • 3
    @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. – Adinia Feb 09 '11 at 08:02
  • 5
    @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. – Thomas Ahle Feb 22 '11 at 12:49
  • 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 . – Muhammad Ahmed AbuTalib May 26 '14 at 07:46
  • Can you help? https://stackoverflow.com/questions/62624070/how-to-priorly-get-future-glide-image-size-which-will-be-stored-in-cache-in-andr – Priyanka Singh Jun 28 '20 at 17:04
118

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();
}
Rahul
  • 3,293
  • 2
  • 31
  • 43
TalkLittle
  • 8,866
  • 6
  • 54
  • 51
  • 4
    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_ Jan 18 '11 at 08:06
  • http://developer.android.com/reference/java/lang/ref/SoftReference.html 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. – vokilam Feb 26 '13 at 11:48
  • 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. – j2emanue Jul 21 '13 at 17:54
  • @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. – TalkLittle Jul 22 '13 at 00:13
108

Picasso

Use Jake Wharton's Picasso Library. (A Perfect ImageLoading Library from the developer of ActionBarSherlock)

A powerful image downloading and caching library for Android.

Images add much-needed context and visual flair to Android applications. Picasso allows for hassle-free image loading in your application—often in one line of code!

Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);

Many common pitfalls of image loading on Android are handled automatically by Picasso:

Handling ImageView recycling and download cancellation in an adapter. Complex image transformations with minimal memory use. Automatic memory and disk caching.

Picasso Jake Wharton's Library

Glide

Glide is a fast and efficient open-source media management framework for Android that wraps media decoding, memory and disk caching, and resource pooling into a simple and easy-to-use interface.

Glide supports fetching, decoding, and displaying video stills, images, and animated GIFs. Glide includes a flexible API that allows developers to plug into almost any network stack. By default, Glide uses a custom HttpUrlConnection based stack but also includes utility libraries plug-in to Google's Volley project or Square's OkHttp library instead.

Glide.with(this).load("your-url-here").into(imageView);

Glide's primary focus is on making scrolling any kind of a list of images as smooth and fast as possible, but Glide is also effective for almost any case where you need to fetch, resize, and display a remote image.

Glide Image Loading Library

Fresco by Facebook

Fresco is a powerful system for displaying images in Android applications.

Fresco takes care of image loading and display, so you don't have to. It will load images from the network, local storage, or local resources, and display a placeholder until the image has arrived. It has two levels of cache; one in memory and another in internal storage.

Fresco Github

In Android 4.x and lower, Fresco puts images in a special region of Android memory. This lets your application run faster - and suffer the dreaded OutOfMemoryError much less often.

Fresco Documentation

Rahul
  • 3,293
  • 2
  • 31
  • 43
Ashwin S Ashok
  • 3,623
  • 2
  • 29
  • 36
88

High-performance loader - after examining the methods suggested here, I used Ben's solution with some changes -

  1. I realized that working with drawable is faster than with bitmaps so I uses drawable instead

  2. Using SoftReference is great, but it makes the cached image to be deleted too often, so I added a Linked list that holds images references, preventing the image to be deleted, until it reached a predefined size

  3. To open the InputStream I used java.net.URLConnection which allows me to use web cache (you need to set a response cache first, but that's another story)

My code:

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;

public class DrawableBackgroundDownloader {    

private final Map<String, SoftReference<Drawable>> mCache = new HashMap<String, SoftReference<Drawable>>();   
private final LinkedList <Drawable> mChacheController = new LinkedList <Drawable> ();
private ExecutorService mThreadPool;  
private final Map<ImageView, String> mImageViews = Collections.synchronizedMap(new WeakHashMap<ImageView, String>());  

public static int MAX_CACHE_SIZE = 80; 
public int THREAD_POOL_SIZE = 3;

/**
 * Constructor
 */
public DrawableBackgroundDownloader() {  
    mThreadPool = Executors.newFixedThreadPool(THREAD_POOL_SIZE);  
}  


/**
 * Clears all instance data and stops running threads
 */
public void Reset() {
    ExecutorService oldThreadPool = mThreadPool;
    mThreadPool = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
    oldThreadPool.shutdownNow();

    mChacheController.clear();
    mCache.clear();
    mImageViews.clear();
}  

public void loadDrawable(final String url, final ImageView imageView,Drawable placeholder) {  
    mImageViews.put(imageView, url);  
    Drawable drawable = getDrawableFromCache(url);  

    // check in UI thread, so no concurrency issues  
    if (drawable != null) {  
        //Log.d(null, "Item loaded from mCache: " + url);  
        imageView.setImageDrawable(drawable);  
    } else {  
        imageView.setImageDrawable(placeholder);  
        queueJob(url, imageView, placeholder);  
    }  
} 


private Drawable getDrawableFromCache(String url) {  
    if (mCache.containsKey(url)) {  
        return mCache.get(url).get();  
    }  

    return null;  
}

private synchronized void putDrawableInCache(String url,Drawable drawable) {  
    int chacheControllerSize = mChacheController.size();
    if (chacheControllerSize > MAX_CACHE_SIZE) 
        mChacheController.subList(0, MAX_CACHE_SIZE/2).clear();

    mChacheController.addLast(drawable);
    mCache.put(url, new SoftReference<Drawable>(drawable));

}  

private void queueJob(final String url, final ImageView imageView,final Drawable placeholder) {  
    /* Create handler in UI thread. */  
    final Handler handler = new Handler() {  
        @Override  
        public void handleMessage(Message msg) {  
            String tag = mImageViews.get(imageView);  
            if (tag != null && tag.equals(url)) {
                if (imageView.isShown())
                    if (msg.obj != null) {
                        imageView.setImageDrawable((Drawable) msg.obj);  
                    } else {  
                        imageView.setImageDrawable(placeholder);  
                        //Log.d(null, "fail " + url);  
                    } 
            }  
        }  
    };  

    mThreadPool.submit(new Runnable() {  
        @Override  
        public void run() {  
            final Drawable bmp = downloadDrawable(url);
            // if the view is not visible anymore, the image will be ready for next time in cache
            if (imageView.isShown())
            {
                Message message = Message.obtain();  
                message.obj = bmp;
                //Log.d(null, "Item downloaded: " + url);  

                handler.sendMessage(message);
            }
        }  
    });  
}  



private Drawable downloadDrawable(String url) {  
    try {  
        InputStream is = getInputStream(url);

        Drawable drawable = Drawable.createFromStream(is, url);
        putDrawableInCache(url,drawable);  
        return drawable;  

    } catch (MalformedURLException e) {  
        e.printStackTrace();  
    } catch (IOException e) {  
        e.printStackTrace();  
    }  

    return null;  
}  


private InputStream getInputStream(String urlString) throws MalformedURLException, IOException {
    URL url = new URL(urlString);
    URLConnection connection;
    connection = url.openConnection();
    connection.setUseCaches(true); 
    connection.connect();
    InputStream response = connection.getInputStream();

    return response;
}
}
Rahul
  • 3,293
  • 2
  • 31
  • 43
Asaf Pinhassi
  • 15,177
  • 12
  • 106
  • 130
  • Works great! BTW there's a typo in the class name. – Mullins Dec 07 '11 at 14:25
  • 6
    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;` – Michael Reed Jan 08 '12 at 05:01
  • 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 Feb 07 '12 at 15:53
  • 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 http://stackoverflow.com/questions/4620061/how-to-create-lifo-executor – Juan Hernandez Feb 07 '12 at 16:19
  • To implement the above with LIFO see http://stackoverflow.com/questions/6107609/executor-service-with-lifo-ordering/8272674#8272674 "Create a LifoBlockingDeque class" – Asaf Pinhassi Feb 08 '12 at 05:15
  • 10
    @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. – SilithCrowe Mar 20 '12 at 15:21
  • @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. – Michael Reed Sep 05 '12 at 23:40
  • this worked perfectly. Now im saved from SkImageDecoder::Factory returned null error. which is painstaking from long time... Thank you somuch...@Pinhassi – praveenb Oct 16 '12 at 09:21
  • The solution doesn't use asyc tast because the thread pool allows to download nultipile images simultaneously – Asaf Pinhassi Dec 30 '12 at 06:51
  • @Michael Reed: can use ctr+shift+O to perform auto import. – CoDe Sep 23 '13 at 11:20
82

I have followed this Android Training and I think it does an excellent job at downloading images without blocking the main UI. It also handles caching and dealing with scrolling through many images: Loading Large Bitmaps Efficiently

jnthnjns
  • 8,962
  • 4
  • 42
  • 65
Etienne Lawlor
  • 6,817
  • 18
  • 77
  • 89
  • 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](http://goo.gl/1TGHi). – mkuech Jan 31 '13 at 05:28
  • 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. – Gautam Apr 25 '13 at 16:58
73

1. Picasso allows for hassle-free image loading in your application—often in one line of code!

Use Gradle:

implementation 'com.squareup.picasso:picasso:(insert latest version)'

Just one line of code!

Picasso.get().load("http://i.imgur.com/DvpvklR.png").into(imageView);

2. Glide An image loading and caching library for Android focused on smooth scrolling

Use Gradle:

repositories {
  mavenCentral() 
  google()
}

dependencies {
   implementation 'com.github.bumptech.glide:glide:4.11.0'
   annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
}

// For a simple view:

  Glide.with(this).load("http://i.imgur.com/DvpvklR.png").into(imageView);

3. fresco is a powerful system for displaying images in Android applications.Fresco takes care of image loading and display, so you don't have to.

Getting Started with Fresco

chiragkyada
  • 3,585
  • 3
  • 17
  • 18
  • This tutorial may help you more for PICASOO :- http://www.androidtutorialshub.com/picasso-image-loading-library-for-android-tutorial/ and GLIDE :- http://www.androidtutorialshub.com/glide-image-loading-library-for-android-tutorial/ – lalit vasan May 10 '17 at 06:54
56

I've written a tutorial that explains how to do lazy-loading of images in a listview. I go into some detail about the issues of recycling and concurrency. I also use a fixed thread pool to prevent spawning a lot of threads.

Lazy loading of images in Listview Tutorial

Trying Tobemyself
  • 3,668
  • 3
  • 28
  • 43
Ben Ruijl
  • 4,973
  • 3
  • 31
  • 44
46

The way I do it is by launching a thread to download the images in the background and hand it a callback for each list item. When an image is finished downloading it calls the callback which updates the view for the list item.

This method doesn't work very well when you're recycling views however.

jasonhudgins
  • 2,815
  • 1
  • 25
  • 20
  • 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. – James A Wilson Feb 15 '09 at 00:40
  • can you please elaborate. I am new to android development. Thanks for the tips though – lostInTransit Feb 15 '09 at 14:23
  • 14
    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. – Fedor Jul 01 '10 at 00:04
  • Fedor, agreed, I usually use a queue and a thread pool, that's the best way to go imo. – jasonhudgins Jul 04 '10 at 22:48
36

I just want to add one more good example, XML Adapters. As it's is used by Google and I am also using the same logic to avoid an OutOfMemory error.

Basically this ImageDownloader is your answer (as it covers most of your requirements). Some you can also implement in that.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Arslan Anwar
  • 18,746
  • 19
  • 76
  • 105
  • 3
    ImageDownloader class not get complied: see the solution below http://code.google.com/p/parleys-android-nextgen/issues/detail?id=1 – Sam Feb 04 '12 at 04:48
33

I have been using NetworkImageView from the new Android Volley Library com.android.volley.toolbox.NetworkImageView, and it seems to be working pretty well. Apparently, this is the same view that is used in Google Play and other new Google applications. Definitely worth checking out.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
droidment
  • 555
  • 5
  • 7
  • 2
    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 – Alexander Sidikov Pfeif Sep 07 '14 at 22:19
33

This is a common problem on Android that has been solved in many ways by many people. In my opinion the best solution I've seen is the relatively new library called Picasso. Here are the highlights:

  • Open source, but headed up by Jake Wharton of ActionBarSherlock fame.
  • Asynchronously load images from network or app resources with one line of code
  • Automatic ListView detection
  • Automatic disk and memory caching
  • Can do custom transformations
  • Lots of configurable options
  • Super simple API
  • Frequently updated
howettl
  • 12,419
  • 13
  • 56
  • 91
29

Well, image loading time from the Internet has many solutions. You may also use the library Android-Query. It will give you all the required activity. Make sure what you want to do and read the library wiki page. And solve the image loading restriction.

This is my code:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    View v = convertView;
    if (v == null) {
        LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        v = vi.inflate(R.layout.row, null);
    }

    ImageView imageview = (ImageView) v.findViewById(R.id.icon);
    AQuery aq = new AQuery(convertView);

    String imageUrl = "http://www.vikispot.com/z/images/vikispot/android-w.png";

    aq.id(imageview).progress(this).image(imageUrl, true, true, 0, 0, new BitmapAjaxCallback() {
        @Override
        public void callback(String url, ImageView iv, Bitmap bm, AjaxStatus status) {
            iv.setImageBitmap(bm);
        }
    ));

    return v;
}

It should be solve your lazy loading problem.

Ziem
  • 6,579
  • 8
  • 53
  • 86
Rahul Rawat
  • 999
  • 2
  • 17
  • 40
  • 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:https://code.google.com/archive/p/android-query/downloads – Selim Raza Apr 24 '17 at 09:13
27

I think this issue is very popular among Android developers, and there are plenty of such libraries that claims to resolve this issue, but only a few of them seems to be on the mark. AQuery is one such library, but it is better than most of them in all aspects and is worth trying for.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
23

You must try this Universal Loader is best. I am using this after done many RnD on lazy loading .

Universal Image Loader

Features

  • Multithread image loading (async or sync)
  • Wide customization of ImageLoader's configuration (thread executors, downloader, decoder, memory and disk cache, display image options, etc.)
  • Many customization options for every display image call (stub images, caching switch, decoding options, Bitmap processing and displaying, etc.)
  • Image caching in memory and/or on disk (device's file system or SD card)
  • Listening loading process (including downloading progress)

Android 2.0+ support

enter image description here

Girish Patel
  • 1,270
  • 4
  • 16
  • 30
22

Have a look at Shutterbug, Applidium's lightweight SDWebImage (a nice library on iOS) port to Android. It supports asynchronous caching, stores failed URLs, handles concurrency well, and helpful subclasses are included.

Pull requests (and bug reports) are welcome, too!

PatrickNLT
  • 4,075
  • 1
  • 25
  • 32
18

DroidParts has ImageFetcher that requires zero configuration to get started.

  • Uses a disk & in-memory Least Recently Used (LRU) cache.
  • Efficiently decodes images.
  • Supports modifying bitmaps in background thread.
  • Has simple cross-fade.
  • Has image loading progress callback.

Clone DroidPartsGram for an example:

Enter image description here

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
yanchenko
  • 56,576
  • 33
  • 147
  • 165
  • 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? http://stackoverflow.com/questions/21089147/recycling-issues-in-arrayadapter-using-imagefetcher Thanks =] – masha Jan 13 '14 at 10:37
17

Novoda also has a great lazy image loading library and many apps like Songkick, Podio, SecretDJ and ImageSearch use their library.

Their library is hosted here on Github and they have a pretty active issues tracker as well. Their project seems to be pretty active too, with over 300+ commits at the time of writing this reply.

Soham
  • 4,940
  • 3
  • 31
  • 48
  • 2
    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. – Nicolas Jafelle Apr 15 '13 at 15:34
17

Just a quick tip for someone who is in indecision regarding what library to use for lazy-loading images:

There are four basic ways.

  1. DIY => Not the best solution but for a few images and if you want to go without the hassle of using others libraries

  2. Volley's Lazy Loading library => From guys at android. It is nice and everything but is poorly documented and hence is a problem to use.

  3. Picasso: A simple solution that just works, you can even specify the exact image size you want to bring in. It is very simple to use but might not be very "performant" for apps that has to deal with humongous amounts of images.

  4. UIL: The best way to lazy load images. You can cache images(you need permission of course), initialize the loader once, then have your work done. The most mature asynchronous image loading library I have ever seen so far.

Bijay Koirala
  • 525
  • 5
  • 9
16

If you want to display Shimmer layout like Facebook there is a official facebook library for that. FaceBook Shimmer Android

It takes care of everything, You just need to put your desired design code in nested manner in shimmer frame. Here is a sample code.

<com.facebook.shimmer.ShimmerFrameLayout
     android:id=“@+id/shimmer_view_container”
     android:layout_width=“wrap_content”
     android:layout_height="wrap_content"
     shimmer:duration="1000">

 <here will be your content to display />

</com.facebook.shimmer.ShimmerFrameLayout>

And here is the java code for it.

ShimmerFrameLayout shimmerContainer = (ShimmerFrameLayout) findViewById(R.id.shimmer_view_container);
shimmerContainer.startShimmerAnimation();

Add this dependency in your gradle file.

implementation 'com.facebook.shimmer:shimmer:0.1.0@aar'

Here is how it looks like.Shimmer Android screenshot

recnac
  • 3,744
  • 6
  • 24
  • 46
Zankrut Parmar
  • 1,872
  • 1
  • 13
  • 28
15

Check my fork of LazyList. Basically, I improve the LazyList by delaying the call of the ImageView and create two methods:

  1. When you need to put something like "Loading image..."
  2. When you need to show the downloaded image.

I also improved the ImageLoader by implementing a singleton in this object.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Nicolas Jafelle
  • 2,661
  • 2
  • 24
  • 30
12

All above code have their own worth but with my personal experience just give a try with Picasso.

Picasso is a library specifically for this purpose, in-fact it will manage cache and all other network operations automatically.You will have to add library in your project and just write a single line of code to load image from remote URL.

Please visit here : http://code.tutsplus.com/tutorials/android-sdk-working-with-picasso--cms-22149

Akber
  • 521
  • 4
  • 10
11

Use the glide library. It worked for me and will work for your code too.It works for both images as well as gifs too.

ImageView imageView = (ImageView) findViewById(R.id.test_image); 
    GlideDrawableImageViewTarget imagePreview = new GlideDrawableImageViewTarget(imageView);
    Glide
            .with(this)
            .load(url)
            .listener(new RequestListener<String, GlideDrawable>() {
                @Override
                public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {                       
                    return false;
                }

                @Override
                public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
                    return false;
                }
            })
            .into(imagePreview);
}
Saket Kumar
  • 463
  • 8
  • 22
8

I can recommend a different way that works like a charm: Android Query.

You can download that JAR file from here

AQuery androidAQuery = new AQuery(this);

As an example:

androidAQuery.id(YOUR IMAGEVIEW).image(YOUR IMAGE TO LOAD, true, true, getDeviceWidth(), ANY DEFAULT IMAGE YOU WANT TO SHOW);

It's very fast and accurate, and using this you can find many more features like animation when loading, getting a bitmap (if needed), etc.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Pratik Dasa
  • 7,439
  • 4
  • 30
  • 44
8

Give Aquery a try. It has amazingly simple methods to load and cache images asynchronously.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
user2779311
  • 1,688
  • 4
  • 23
  • 31
7

URLImageViewHelper is an amazing library that helps you to do that.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
DiegoAlt
  • 89
  • 2
  • 2
5
public class ImageDownloader {

Map<String, Bitmap> imageCache;

public ImageDownloader() {
    imageCache = new HashMap<String, Bitmap>();

}

// download function
public void download(String url, ImageView imageView) {
    if (cancelPotentialDownload(url, imageView)) {

        // Caching code right here
        String filename = String.valueOf(url.hashCode());
        File f = new File(getCacheDirectory(imageView.getContext()),
                filename);

        // Is the bitmap in our memory cache?
        Bitmap bitmap = null;

        bitmap = (Bitmap) imageCache.get(f.getPath());

        if (bitmap == null) {

            bitmap = BitmapFactory.decodeFile(f.getPath());

            if (bitmap != null) {
                imageCache.put(f.getPath(), bitmap);
            }

        }
        // No? download it
        if (bitmap == null) {
            try {
                BitmapDownloaderTask task = new BitmapDownloaderTask(
                        imageView);
                DownloadedDrawable downloadedDrawable = new DownloadedDrawable(
                        task);
                imageView.setImageDrawable(downloadedDrawable);
                task.execute(url);
            } catch (Exception e) {
                Log.e("Error==>", e.toString());
            }

        } else {
            // Yes? set the image
            imageView.setImageBitmap(bitmap);
        }
    }
}

// cancel a download (internal only)
private static boolean cancelPotentialDownload(String url,
        ImageView imageView) {
    BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);

    if (bitmapDownloaderTask != null) {
        String bitmapUrl = bitmapDownloaderTask.url;
        if ((bitmapUrl == null) || (!bitmapUrl.equals(url))) {
            bitmapDownloaderTask.cancel(true);
        } else {
            // The same URL is already being downloaded.
            return false;
        }
    }
    return true;
}

// gets an existing download if one exists for the imageview
private static BitmapDownloaderTask getBitmapDownloaderTask(
        ImageView imageView) {
    if (imageView != null) {
        Drawable drawable = imageView.getDrawable();
        if (drawable instanceof DownloadedDrawable) {
            DownloadedDrawable downloadedDrawable = (DownloadedDrawable) drawable;
            return downloadedDrawable.getBitmapDownloaderTask();
        }
    }
    return null;
}

// our caching functions
// Find the dir to save cached images
private static File getCacheDirectory(Context context) {
    String sdState = android.os.Environment.getExternalStorageState();
    File cacheDir;

    if (sdState.equals(android.os.Environment.MEDIA_MOUNTED)) {
        File sdDir = android.os.Environment.getExternalStorageDirectory();

        // TODO : Change your diretcory here
        cacheDir = new File(sdDir, "data/ToDo/images");
    } else
        cacheDir = context.getCacheDir();

    if (!cacheDir.exists())
        cacheDir.mkdirs();
    return cacheDir;
}

private void writeFile(Bitmap bmp, File f) {
    FileOutputStream out = null;

    try {
        out = new FileOutputStream(f);
        bmp.compress(Bitmap.CompressFormat.PNG, 80, out);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            if (out != null)
                out.close();
        } catch (Exception ex) {
        }
    }
}

// download asynctask
public class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> {
    private String url;
    private final WeakReference<ImageView> imageViewReference;

    public BitmapDownloaderTask(ImageView imageView) {
        imageViewReference = new WeakReference<ImageView>(imageView);
    }

    @Override
    // Actual download method, run in the task thread
    protected Bitmap doInBackground(String... params) {
        // params comes from the execute() call: params[0] is the url.
        url = (String) params[0];
        return downloadBitmap(params[0]);
    }

    @Override
    // Once the image is downloaded, associates it to the imageView
    protected void onPostExecute(Bitmap bitmap) {
        if (isCancelled()) {
            bitmap = null;
        }

        if (imageViewReference != null) {
            ImageView imageView = imageViewReference.get();
            BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
            // Change bitmap only if this process is still associated with
            // it
            if (this == bitmapDownloaderTask) {
                imageView.setImageBitmap(bitmap);

                // cache the image

                String filename = String.valueOf(url.hashCode());
                File f = new File(
                        getCacheDirectory(imageView.getContext()), filename);

                imageCache.put(f.getPath(), bitmap);

                writeFile(bitmap, f);
            }
        }
    }

}

static class DownloadedDrawable extends ColorDrawable {
    private final WeakReference<BitmapDownloaderTask> bitmapDownloaderTaskReference;

    public DownloadedDrawable(BitmapDownloaderTask bitmapDownloaderTask) {
        super(Color.WHITE);
        bitmapDownloaderTaskReference = new WeakReference<BitmapDownloaderTask>(
                bitmapDownloaderTask);
    }

    public BitmapDownloaderTask getBitmapDownloaderTask() {
        return bitmapDownloaderTaskReference.get();
    }
}

// the actual download code
static Bitmap downloadBitmap(String url) {
    HttpParams params = new BasicHttpParams();
    params.setParameter(CoreProtocolPNames.PROTOCOL_VERSION,
            HttpVersion.HTTP_1_1);
    HttpClient client = new DefaultHttpClient(params);
    final HttpGet getRequest = new HttpGet(url);

    try {
        HttpResponse response = client.execute(getRequest);
        final int statusCode = response.getStatusLine().getStatusCode();
        if (statusCode != HttpStatus.SC_OK) {
            Log.w("ImageDownloader", "Error " + statusCode
                    + " while retrieving bitmap from " + url);
            return null;
        }

        final HttpEntity entity = response.getEntity();
        if (entity != null) {
            InputStream inputStream = null;
            try {
                inputStream = entity.getContent();
                final Bitmap bitmap = BitmapFactory
                        .decodeStream(inputStream);
                return bitmap;
            } finally {
                if (inputStream != null) {
                    inputStream.close();
                }
                entity.consumeContent();
            }
        }
    } catch (Exception e) {
        // Could provide a more explicit error message for IOException or
        // IllegalStateException
        getRequest.abort();
        Log.w("ImageDownloader", "Error while retrieving bitmap from "
                + url + e.toString());
    } finally {
        if (client != null) {
            // client.close();
        }
    }
    return null;
 }
}
Rupesh Yadav
  • 12,096
  • 4
  • 53
  • 70
Nikhil Gupta
  • 61
  • 1
  • 2
5

I had this issue and implemented lruCache. I believe you need API 12 and above or use the compatiblity v4 library. lurCache is fast memory, but it also has a budget, so if you're worried about that you can use a diskcache... It's all described in Caching Bitmaps.

I'll now provide my implementation which is a singleton I call from anywhere like this:

//Where the first is a string and the other is a imageview to load.

DownloadImageTask.getInstance().loadBitmap(avatarURL, iv_avatar);

Here's the ideal code to cache and then call the above in getView of an adapter when retrieving the web image:

public class DownloadImageTask {

    private LruCache<String, Bitmap> mMemoryCache;

    /* Create a singleton class to call this from multiple classes */

    private static DownloadImageTask instance = null;

    public static DownloadImageTask getInstance() {
        if (instance == null) {
            instance = new DownloadImageTask();
        }
        return instance;
    }

    //Lock the constructor from public instances
    private DownloadImageTask() {

        // Get max available VM memory, exceeding this amount will throw an
        // OutOfMemory exception. Stored in kilobytes as LruCache takes an
        // int in its constructor.
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

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

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

    public void loadBitmap(String avatarURL, ImageView imageView) {
        final String imageKey = String.valueOf(avatarURL);

        final Bitmap bitmap = getBitmapFromMemCache(imageKey);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
        } else {
            imageView.setImageResource(R.drawable.ic_launcher);

            new DownloadImageTaskViaWeb(imageView).execute(avatarURL);
        }
    }

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

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

    /* A background process that opens a http stream and decodes a web image. */

    class DownloadImageTaskViaWeb extends AsyncTask<String, Void, Bitmap> {
        ImageView bmImage;

        public DownloadImageTaskViaWeb(ImageView bmImage) {
            this.bmImage = bmImage;
        }

        protected Bitmap doInBackground(String... urls) {

            String urldisplay = urls[0];
            Bitmap mIcon = null;
            try {
                InputStream in = new java.net.URL(urldisplay).openStream();
                mIcon = BitmapFactory.decodeStream(in);

            } 
            catch (Exception e) {
                Log.e("Error", e.getMessage());
                e.printStackTrace();
            }

            addBitmapToMemoryCache(String.valueOf(urldisplay), mIcon);

            return mIcon;
        }

        /* After decoding we update the view on the main UI. */
        protected void onPostExecute(Bitmap result) {
            bmImage.setImageBitmap(result);
        }
    }
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
j2emanue
  • 60,549
  • 65
  • 286
  • 456
5

You can try the Aquery Android library for lazy loading image and listview... The below code may help you..... download library from here.

AQuery aq = new AQuery(mContext);
aq.id(R.id.image1).image("http://data.whicdn.com/images/63995806/original.jpg");
Chirag Ghori
  • 4,231
  • 2
  • 20
  • 35
4

I use droidQuery. There are two mechanisms for loading an image from a URL. The first (shorthand) is simply:

$.with(myView).image(url);

This can be added into an ArrayAdapter's getView(...) method very easily.


The longhand method will give a lot more control, and has options not even discussed here (such as cacheing and callbacks), but a basic implementation that specifies the output size as 200px x 200px can be found here:

$.ajax(new AjaxOptions().url(url)
    .type("GET")
    .dataType("image")
    .imageWidth(200).imageHeight(200)
    .success(new Function() {
        @Override
        public void invoke($ droidQuery, Object... params) {
            myImageView.setImageBitmap((Bitmap) params[0]);
        }
    })
    .error(new Function() {
        @Override
        public void invoke($ droidQuery, Object... params) {
            AjaxError e = (AjaxError) params[0];
            Log.e("$", "Error " + e.status + ": " + e.error);
        }
    })
);
Ziem
  • 6,579
  • 8
  • 53
  • 86
Phil
  • 35,852
  • 23
  • 123
  • 164
4

I found the Glide as better option than Picasso. I was using picasso to load around 32 images of size around 200-500KB each and I was always getting OOM. But the Glide solved my all OOM issues.

Piyush
  • 18,895
  • 5
  • 32
  • 63
Sanjeet A
  • 5,171
  • 3
  • 23
  • 40
4

use below class to download and load images in listview.It caches every images once download. Also loads images ad lazy loading.

package com.fudiyoxpress.images;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.widget.ImageView;

import com.fudiyoxpress.R;
import com.fudiyoxpress.config.Config;
import com.fudiyoxpress.twitter.ScaleBitmap;

public class ImageLoader {

    // Initialize MemoryCache
    MemoryCache memoryCache = new MemoryCache();

    FileCache fileCache;

    Context C;

    // Create Map (collection) to store image and image url in key value pair
    private Map<ImageView, String> imageViews = Collections
            .synchronizedMap(new WeakHashMap<ImageView, String>());
    ExecutorService executorService;

    // handler to display images in UI thread
    Handler handler = new Handler();

    public ImageLoader(Context context) {

        C = context;
        fileCache = new FileCache(context);

        // Creates a thread pool that reuses a fixed number of
        // threads operating off a shared unbounded queue.
        executorService = Executors.newFixedThreadPool(5);

    }

    // default image show in list (Before online image download)
    final int stub_id = R.drawable.restlogoplaceholder;

    public void DisplayImage(String url, ImageView imageView, Context context,
            boolean header_flag) {

        Bitmap largeIcon = BitmapFactory.decodeResource(context.getResources(),
                R.drawable.restlogoplaceholder);
        header_flag = false;
        // Store image and url in Map
        imageViews.put(imageView, url);

        // Check image is stored in MemoryCache Map or not (see
        // MemoryCache.java)
        Bitmap bitmap = memoryCache.get(url);

        if (bitmap != null) {
            // if image is stored in MemoryCache Map then
            // Show image in listview row
            Bitmap b = ScaleBitmap
                    .getScaledBitmap(context, bitmap, header_flag);
            imageView.setImageBitmap(b);

        } else {
            // queue Photo to download from url
            queuePhoto(url, imageView, header_flag);

            // Before downloading image show default image
            imageView.setImageBitmap(ScaleBitmap.getScaledBitmap(context,
                    largeIcon, header_flag));

        }
    }



    private void queuePhoto(String url, ImageView imageView, boolean header_flag) {
        // Store image and url in PhotoToLoad object
        PhotoToLoad p = new PhotoToLoad(url, imageView, header_flag);

        // pass PhotoToLoad object to PhotosLoader runnable class
        // and submit PhotosLoader runnable to executers to run runnable
        // Submits a PhotosLoader runnable task for execution

        executorService.submit(new PhotosLoader(p));
    }

    // Task for the queue
    private class PhotoToLoad {
        public String url;
        public ImageView imageView;
        public boolean b;

        public PhotoToLoad(String u, ImageView i, boolean header_flag) {
            url = u;
            imageView = i;
            b = header_flag;
        }
    }

    class PhotosLoader implements Runnable {
        PhotoToLoad photoToLoad;

        PhotosLoader(PhotoToLoad photoToLoad) {
            this.photoToLoad = photoToLoad;
        }

        @Override
        public void run() {
            try {
                // Check if image already downloaded
                if (imageViewReused(photoToLoad))
                    return;
                // download image from web url
                Bitmap bmp = getBitmap(photoToLoad.url);

                // set image data in Memory Cache
                memoryCache.put(photoToLoad.url, bmp);

                if (imageViewReused(photoToLoad))
                    return;

                // Get bitmap to display
                BitmapDisplayer bd = new BitmapDisplayer(bmp, photoToLoad);

                // Causes the Runnable bd (BitmapDisplayer) to be added to the
                // message queue.
                // The runnable will be run on the thread to which this handler
                // is attached.
                // BitmapDisplayer run method will call
                handler.post(bd);

            } catch (Throwable th) {
                // th.printStackTrace();
            }
        }
    }

    private Bitmap getBitmap(String url) {
        File f = fileCache.getFile(url);

        // from SD cache
        // CHECK : if trying to decode file which not exist in cache return null
        Bitmap b = decodeFile(f);
        if (b != null)
            return b;

        // Download image file from web
        try {

            // // download the image
            Bitmap bitmap = null;

            URL imageURL = null;
            try {

                imageURL = new URL(Config.WEB_URL + "/ServeBlob?id=" + url);

                HttpURLConnection connection = (HttpURLConnection) imageURL
                        .openConnection();
                connection.setDoInput(true);
                connection.connect();
                // if(!(new File(imageURL.toString())).exists())
                // {
                // imageURL=new URL("");
                // }
                InputStream inputStream = connection.getInputStream();

                // Constructs a new FileOutputStream that writes to
                // file
                // if file not exist then it will create file
                OutputStream os = new FileOutputStream(f);

                // See Utils class CopyStream method
                // It will each pixel from input stream and
                // write pixels to output stream (file)
                Utils.CopyStream(inputStream, os);

                os.close();

                BitmapFactory.Options options = new BitmapFactory.Options();
                options.inSampleSize = 8;

                bitmap = BitmapFactory.decodeStream(inputStream, null, options);

            } catch (IOException e) {

                // e.printStackTrace();
            }

            // Now file created and going to resize file with defined height
            // Decodes image and scales it to reduce memory consumption
            bitmap = decodeFile(f);

            return bitmap;

        } catch (Throwable ex) {
            ex.printStackTrace();
            if (ex instanceof OutOfMemoryError)
                memoryCache.clear();
            return null;
        }
    }

    // Decodes image and scales it to reduce memory consumption
    private Bitmap decodeFile(File f) {

        try {

            // Decode image size
            BitmapFactory.Options o = new BitmapFactory.Options();
            o.inJustDecodeBounds = true;
            FileInputStream stream1 = new FileInputStream(f);
            BitmapFactory.decodeStream(stream1, null, o);
            stream1.close();

            // Find the correct scale value. It should be the power of 2.

            // Set width/height of recreated image
            final int REQUIRED_SIZE = 85;

            int width_tmp = o.outWidth, height_tmp = o.outHeight;
            int scale = 1;
            while (true) {
                if (width_tmp / 2 < REQUIRED_SIZE
                        || height_tmp / 2 < REQUIRED_SIZE)
                    break;
                width_tmp /= 2;
                height_tmp /= 2;
                scale *= 2;
            }

            // decode with current scale values
            BitmapFactory.Options o2 = new BitmapFactory.Options();
            o2.inSampleSize = scale;
            FileInputStream stream2 = new FileInputStream(f);
            Bitmap bitmap = BitmapFactory.decodeStream(stream2, null, o2);
            stream2.close();
            return bitmap;

        } catch (FileNotFoundException e) {
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    boolean imageViewReused(PhotoToLoad photoToLoad) {

        String tag = imageViews.get(photoToLoad.imageView);
        // Check url is already exist in imageViews MAP
        if (tag == null || !tag.equals(photoToLoad.url))
            return true;
        return false;
    }

    // Used to display bitmap in the UI thread
    class BitmapDisplayer implements Runnable {
        Bitmap bitmap;
        PhotoToLoad photoToLoad;

        public BitmapDisplayer(Bitmap b, PhotoToLoad p) {
            bitmap = b;
            photoToLoad = p;
        }

        public void run() {
            if (imageViewReused(photoToLoad))
                return;

            // Show bitmap on UI
            if (bitmap != null) {
                photoToLoad.imageView.setImageBitmap(ScaleBitmap
                        .getScaledBitmap(C, bitmap, photoToLoad.b));
            } else {

            }
            // photoToLoad.imageView.setImageResource(stub_id);

        }
    }

    public void clearCache() {
        // Clear cache directory downloaded images and stored data in maps
        memoryCache.clear();
        fileCache.clear();
    }

}




package com.fudiyoxpress.images;

import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import android.graphics.Bitmap;
import android.util.Log;

public class MemoryCache {

    private static final String TAG = "MemoryCache";

    //Last argument true for LRU ordering
    private Map<String, Bitmap> cache = Collections.synchronizedMap(
            new LinkedHashMap<String, Bitmap>(10,1.5f,true));

   //current allocated size
    private long size=0; 

    //max memory cache folder used to download images in bytes
    private long limit = 1000000; 

    public MemoryCache(){

        //use 25% of available heap size
        setLimit(Runtime.getRuntime().maxMemory()/4);
    }

    public void setLimit(long new_limit){

        limit=new_limit;
        Log.i(TAG, "MemoryCache will use up to "+limit/1024./1024.+"MB");
    }

    public Bitmap get(String id){
        try{
            if(!cache.containsKey(id))
                return null;
            //NullPointerException sometimes happen here http://code.google.com/p/osmdroid/issues/detail?id=78 
            return cache.get(id);
        }catch(NullPointerException ex){
            ex.printStackTrace();
            return null;
        }
    }

    public void put(String id, Bitmap bitmap){
        try{
            if(cache.containsKey(id))
                size-=getSizeInBytes(cache.get(id));
            cache.put(id, bitmap);
            size+=getSizeInBytes(bitmap);
            checkSize();
        }catch(Throwable th){
            th.printStackTrace();
        }
    }

    private void checkSize() {
        Log.i(TAG, "cache size="+size+" length="+cache.size());
        if(size>limit){
            Iterator<Entry<String, Bitmap>> iter=cache.entrySet().iterator();//least recently accessed item will be the first one iterated  
            while(iter.hasNext()){
                Entry<String, Bitmap> entry=iter.next();
                size-=getSizeInBytes(entry.getValue());
                iter.remove();
                if(size<=limit)
                    break;
            }
            Log.i(TAG, "Clean cache. New size "+cache.size());
        }
    }

    public void clear() {
        try{
            //NullPointerException sometimes happen here http://code.google.com/p/osmdroid/issues/detail?id=78 
            cache.clear();
            size=0;
        }catch(NullPointerException ex){
            ex.printStackTrace();
        }
    }

    long getSizeInBytes(Bitmap bitmap) {
        if(bitmap==null)
            return 0;
        return bitmap.getRowBytes() * bitmap.getHeight();
    }
}




package com.fudiyoxpress.images;

import java.io.InputStream;
import java.io.OutputStream;

public class Utils {
    public static void CopyStream(InputStream is, OutputStream os)
    {
        final int buffer_size=1024;
        try
        {

            byte[] bytes=new byte[buffer_size];
            for(;;)
            {
              //Read byte from input stream

              int count=is.read(bytes, 0, buffer_size);
              if(count==-1)
                  break;

              //Write byte from output stream
              os.write(bytes, 0, count);
            }
        }
        catch(Exception ex){}
    }
}
Bartłomiej Semańczyk
  • 59,234
  • 49
  • 233
  • 358
Jotiram Chavan
  • 375
  • 1
  • 8
3

Some answers have already mentioned using various image libraries like Universal Image Loader and androidimageloader etc. This is an old quesion but for anyone still looking for something like this, there are several such libraries for image loading/caching.

redGREENblue
  • 3,076
  • 8
  • 39
  • 57
3

Another way to do it, is through your adapter in a thread in your getView() method :

Thread pics_thread = new Thread(new Runnable() {
    @Override
    public void run() {
        Bitmap bitmap = getPicture(url);
        if(bitmap != null) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    holder.imageview.setImageBitmap(bitmap);            
                    adapter.notifyDataSetChanged();
                }                       
            });             
        }       
    }                       
});

pics_thread.start();

of course, you should always cache your images to avoid extra operations, you could put your images in a HashMap array, check if the image exists in the array, if not, proceed with the thread or else load the image from you HashMap array. Also always check that you are not leaking memory, bitmaps and drawables are often heavy on memory. It is up to you to optimize your code.

Ziem
  • 6,579
  • 8
  • 53
  • 86
Samet
  • 917
  • 12
  • 26
  • 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. – The Original Android Jun 07 '15 at 08:31
3

You can use some third party library such as Piccaso or Volley for effective lazy loading. You can also create your own by implementing the below

  1. Implement code for downloading the image from the url

  2. Implement caching mechanism for storing and retrieving image(Use LruCache of android for caching)

Piyush
  • 18,895
  • 5
  • 32
  • 63
BalaramNayak
  • 1,295
  • 13
  • 16
2

UPDATE: if you're searching for a solution in 2020 backed by Kotlin Coroutines, try Coil.

Coil is an acronym for Coroutine Image Loader.

Features

  1. Fast: Coil performs a number of optimizations including memory and disk caching, downsampling the image in memory, re-using bitmaps, automatically pausing/cancelling requests, and more.
  2. Lightweight: Coil adds ~2000 methods to your APK (for apps that already use OkHttp and Coroutines), which is comparable to Picasso and significantly less than Glide and Fresco.
  3. Easy to use: Coil's API leverages Kotlin's language features for simplicity and minimal boilerplate.
  4. Modern: Coil is Kotlin-first and uses modern libraries including Coroutines, OkHttp, Okio, and AndroidX Lifecycles.

Gradle Setup:

Coil is available on mavenCentral().

implementation("io.coil-kt:coil:1.0.0")

Quick Start

To load an image into an ImageView, use the load extension function:

// URL
imageView.load("https://www.example.com/image.jpg")

// Resource
imageView.load(R.drawable.image)

// File
imageView.load(File("/path/to/image.jpg"))

or on Background threads

// Coil (suspends the current coroutine; non-blocking and thread safe)
val request = ImageRequest.Builder(context)
    .data(url)
    .size(width, height)
    .build()
val drawable = context.imageLoader.execute(request).drawable

You can also migrate from Picasso/Glide

Full documentation here

Ali Ahsan
  • 985
  • 11
  • 16
1

Except load the data cache asynchronously, you may require the UI cache, like setViewCacheSize

Except the the loading visible item data, you may require to load the approximity-visible item data

AndroidX Paging Library is another option, for example, you can load and cache and display 10,000,000 items to a RecyclerView from a SQLite database. refer to PagedList

Example: Suppose the listview visible item is [6,7,8,9,10], you may require to load [6,7,8,9,10] AND pre-load the item [1, 2, 3, 4, 5] & [11, 12, 13, 14, 15], because user probably scroll to the pre-page or post-page

Yessy
  • 1,172
  • 1
  • 8
  • 13
  • 3
    In addition to your description, please include some code to further improve your answer. – buczek Jun 14 '16 at 16:09
1

Glide

Glide is a fast and efficient open-source media management framework for Android that wraps media decoding, memory and disk caching, and resource pooling into a simple and easy-to-use interface.

Glide supports fetching, decoding, and displaying video stills, images, and animated GIFs. Glide includes a flexible API that allows developers to plug into almost any network stack. By default, Glide uses a custom HttpUrlConnection based stack but also includes utility libraries plug-in to Google's Volley project or Square's OkHttp library instead.

Glide.with(this).load("your-url-here").into(imageView);

Glide's primary focus is on making scrolling any kind of a list of images as smooth and fast as possible, but Glide is also effective for almost any case where you need to fetch, resize, and display a remote image.

Glide Library

Picasso

Use Jake Wharton's Picasso Library. (A Perfect ImageLoading Library from the developer of ActionBarSherlock)

A powerful image downloading and caching library for Android.

Images add much-needed context and visual flair to Android applications. Picasso allows for hassle-free image loading in your application—often in one line of code!

Picasso.with(context).load("your-url-here").into(imageView);

Many common pitfalls of image loading on Android are handled automatically by Picasso:

Handling ImageView recycling and download cancellation in an adapter. Complex image transformations with minimal memory use. Automatic memory and disk caching.

Picasso Library

Rahul
  • 3,293
  • 2
  • 31
  • 43
0

This is how you would do it with Jetpack Compose.

implementation("io.coil-kt:coil-compose:1.3.1") // Add the Coil-Compose library
Image(
    painter = rememberImagePainter("https://www.example.com/image.jpg"),
    contentDescription = "My image description",
    modifier = Modifier.size(128.dp)
)

Thanks to nglauber and Gabriele Mariotti for this and this answer.

Mahozad
  • 18,032
  • 13
  • 118
  • 133