Android – Asynchronous image loading in ListView

By -
Problem: How to load images in ListView asynchronously, and also caching the images?

Update:
For latest stuffs and libraries for the asynchronous image loading in android, read latest article: Be a lazy but a productive android developer – Part 5 – Image loading library.

I am writing this article based on the many requests/feedbacks and I saw many developers on Stackoverflow facing the problem of loading images in ListView asynchronously. This is not an easy task, yes it will be but after reading and implementing the code given in this tutorial :)

If you are newbie in Android development then i would suggest you to refer the articles/tutorials given in ListView category.

And I am sure you have gone through my previous article: Android – Load images from web and caching. where i have talked about loading single images from web and caching the same. Now here today, i will talk about to load images in listview. Just think what if we dont implement asynchronous loading of images? It will suck and user will go away from your application, so do you want the same? If no then i would suggest you to implement Lazy Loading (Load images asynchronously).

If you have gone through my previous article then it would be easy to go through this article. Because in this article we just has to implement ListView and create adapter to load images and data.

Asynchronous image loading in Android

Step 1: create XML layout for ListView.
activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <ListView
        android:id="@+id/listView1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:cacheColorHint="@android:color/transparent"
        android:divider="@android:color/transparent"
        android:dividerHeight="10dp"
        android:fadingEdge="none"
        android:listSelector="@android:color/transparent"
        android:padding="10dp" >
    </ListView>

</LinearLayout>

Step 2: Create Row file for ListView items
row_listview_item.xml
asynchronous image loading in android

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/btn_white_matte" >

    <ImageView
        android:id="@+id/image"
        android:layout_width="50dip"
        android:layout_height="50dip"
        android:scaleType="centerCrop"
        android:src="@drawable/ic_launcher" />

    <TextView
        android:id="@+id/text"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="left|center_vertical"
        android:layout_marginLeft="10dip"
        android:layout_weight="1"
        android:textSize="20dip" />

</LinearLayout>

Step 3: Create adapter for ListView
LazyAdapter.java

package com.technotalkative.loadwebimage;

import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import com.technotalkative.loadwebimage.imageutils.ImageLoader;

public class LazyAdapter extends BaseAdapter {
    
    private Activity activity;
    private String[] data;
    private static LayoutInflater inflater=null;
    public ImageLoader imageLoader; 
    
    public LazyAdapter(Activity a, String[] d) {
        activity = a;
        data=d;
        inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        imageLoader=new ImageLoader(activity.getApplicationContext());
    }

    public int getCount() {
        return data.length;
    }

    public Object getItem(int position) {
        return position;
    }

    public long getItemId(int position) {
        return position;
    }
    
    public View getView(int position, View convertView, ViewGroup parent) {
        View vi=convertView;
        if(convertView==null)
            vi = inflater.inflate(R.layout.row_listview_item, null);

        TextView text=(TextView)vi.findViewById(R.id.text);;
        ImageView image=(ImageView)vi.findViewById(R.id.image);
        text.setText("item "+position);
        imageLoader.DisplayImage(data[position], image);
        return vi;
    }
}

Step 4: Create MainActivity.java file
MainActivity.java

package com.technotalkative.loadwebimage;

import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView;

public class MainActivity extends Activity {
    
    ListView list;
    LazyAdapter adapter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        list=(ListView)findViewById(R.id.listView1);
        adapter=new LazyAdapter(this, imageUrls);
        list.setAdapter(adapter);
    }
    
    @Override
    public void onDestroy()
    {
        list.setAdapter(null);
        super.onDestroy();
    }
    
    private String imageUrls[] = {
    		"https://si0.twimg.com/profile_images/2053165008/ac-new_bigger.jpg",
    		"https://si0.twimg.com/profile_images/1751120073/green-android-rotate-02__1__copy_9_bigger.jpeg",
    		"https://si0.twimg.com/profile_images/2508170683/m8jf0po4imu8t5eemjdd_bigger.png",
    		"https://si0.twimg.com/profile_images/1517737798/aam-twitter-right-final_bigger.png",
    		"https://si0.twimg.com/profile_images/1265264136/twitter_bg3_bigger.png",
    		"https://si0.twimg.com/profile_images/64827025/android-wallpaper6_2560x160_bigger.png",
    		"https://si0.twimg.com/profile_images/839219643/gals_twitter_bigger.png",
    		"https://si0.twimg.com/profile_images/2244328948/ADC4_Twitter_128_bigger.jpg",
    		"https://si0.twimg.com/profile_images/956404323/androinica-avatar_bigger.png",
    		"https://si0.twimg.com/profile_images/1417650153/android-hug_bigger.png",
    		"https://si0.twimg.com/profile_images/1084169260/twitter_logo3_bigger.png",
    		"https://si0.twimg.com/profile_images/895713856/android_large_bigger.png",
    		"https://si0.twimg.com/profile_images/328066023/droid_con150_bigger.jpg",
    		"https://si0.twimg.com/profile_images/909231146/Android_Biz_Man_bigger.png",
    		"https://si0.twimg.com/profile_images/77641093/AndroidPlanet_bigger.png",
    		"https://si0.twimg.com/profile_images/60788468/androffice_bigger.png",
    		"https://si0.twimg.com/profile_images/262620111/logodroid_bigger.png",
    		"https://si0.twimg.com/profile_images/1024243227/Android-Apps_bigger.jpg",
    		"https://si0.twimg.com/profile_images/2172264088/logo-testa-quad_bigger.png",
    		"https://si0.twimg.com/profile_images/1186449790/mestre-android-twitter_bigger.jpg",
    		"https://si0.twimg.com/profile_images/1785885571/androidvenezuela_bigger.png"
    		
    }; 
}

Step 5: Import Required classes

These are the required classes for loading image asynchronously and caching into the local memory storage.
1) ImageLoader
2) MemoryCache
3) FileCache
4) Utils

ImageLoader.java
Using DisplayImage(Url, ImageView) method of ImageLoader class, you can load and cache image. You just need to provide the web url of image and the imageview in which you want to display loaded image.

package com.technotalkative.loadwebimage.imageutils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
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.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.widget.ImageView;

import com.technotalkative.loadwebimage.R;

public class ImageLoader {
    
    MemoryCache memoryCache=new MemoryCache();
    FileCache fileCache;
    private Map<ImageView, String> imageViews=Collections.synchronizedMap(new WeakHashMap<ImageView, String>());
    ExecutorService executorService; 
    
    public ImageLoader(Context context){
        fileCache=new FileCache(context);
        executorService=Executors.newFixedThreadPool(5);
    }
    
    final int stub_id=R.drawable.ic_launcher;
    public void DisplayImage(String url, ImageView imageView)
    {
        imageViews.put(imageView, url);
        Bitmap bitmap=memoryCache.get(url);
        if(bitmap!=null)
            imageView.setImageBitmap(bitmap);
        else
        {
            queuePhoto(url, imageView);
            imageView.setImageResource(stub_id);
        }
    }
        
    private void queuePhoto(String url, ImageView imageView)
    {
        PhotoToLoad p=new PhotoToLoad(url, imageView);
        executorService.submit(new PhotosLoader(p));
    }
    
    private Bitmap getBitmap(String url) 
    {
        File f=fileCache.getFile(url);
        
        //from SD cache
        Bitmap b = decodeFile(f);
        if(b!=null)
            return b;
        
        //from web
        try {
            Bitmap bitmap=null;
            URL imageUrl = new URL(url);
            HttpURLConnection conn = (HttpURLConnection)imageUrl.openConnection();
            conn.setConnectTimeout(30000);
            conn.setReadTimeout(30000);
            conn.setInstanceFollowRedirects(true);
            InputStream is=conn.getInputStream();
            OutputStream os = new FileOutputStream(f);
            Utils.CopyStream(is, os);
            os.close();
            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;
            BitmapFactory.decodeStream(new FileInputStream(f),null,o);
            
            //Find the correct scale value. It should be the power of 2.
            final int REQUIRED_SIZE=70;
            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 inSampleSize
            BitmapFactory.Options o2 = new BitmapFactory.Options();
            o2.inSampleSize=scale;
            return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
        } catch (FileNotFoundException e) {}
        return null;
    }
    
    //Task for the queue
    private class PhotoToLoad
    {
        public String url;
        public ImageView imageView;
        public PhotoToLoad(String u, ImageView i){
            url=u; 
            imageView=i;
        }
    }
    
    class PhotosLoader implements Runnable {
        PhotoToLoad photoToLoad;
        PhotosLoader(PhotoToLoad photoToLoad){
            this.photoToLoad=photoToLoad;
        }
        
        @Override
        public void run() {
            if(imageViewReused(photoToLoad))
                return;
            Bitmap bmp=getBitmap(photoToLoad.url);
            memoryCache.put(photoToLoad.url, bmp);
            if(imageViewReused(photoToLoad))
                return;
            BitmapDisplayer bd=new BitmapDisplayer(bmp, photoToLoad);
            Activity a=(Activity)photoToLoad.imageView.getContext();
            a.runOnUiThread(bd);
        }
    }
    
    boolean imageViewReused(PhotoToLoad photoToLoad){
        String tag=imageViews.get(photoToLoad.imageView);
        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;
            if(bitmap!=null)
                photoToLoad.imageView.setImageBitmap(bitmap);
            else
                photoToLoad.imageView.setImageResource(stub_id);
        }
    }

    public void clearCache() {
        memoryCache.clear();
        fileCache.clear();
    }

}

MemoryCache.java

package com.technotalkative.loadwebimage.imageutils;

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";
    private Map<String, Bitmap> cache=Collections.synchronizedMap(
            new LinkedHashMap<String, Bitmap>(10,1.5f,true));//Last argument true for LRU ordering
    private long size=0;//current allocated size
    private long limit=1000000;//max memory in bytes

    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();
    }
}

FileCache.java
Using FileCache, we will create TTImages_cache folder for caching images into it. Also to load image if synced already. We can use clear() method of FileCache class to clear synced images.

package com.technotalkative.loadwebimage.imageutils;

import java.io.File;
import android.content.Context;

public class FileCache {
    
    private File cacheDir;
    
    public FileCache(Context context){
        //Find the dir to save cached images
        if (android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED))
            cacheDir=new File(android.os.Environment.getExternalStorageDirectory(),"TTImages_cache");
        else
            cacheDir=context.getCacheDir();
        if(!cacheDir.exists())
            cacheDir.mkdirs();
    }
    
    public File getFile(String url){
        //I identify images by hashcode. Not a perfect solution, good for the demo.
        String filename=String.valueOf(url.hashCode());
        //Another possible solution (thanks to grantland)
        //String filename = URLEncoder.encode(url);
        File f = new File(cacheDir, filename);
        return f;
        
    }
    
    public void clear(){
        File[] files=cacheDir.listFiles();
        if(files==null)
            return;
        for(File f:files)
            f.delete();
    }

}

Utils.java

package com.technotalkative.loadwebimage.imageutils;

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(;;)
            {
              int count=is.read(bytes, 0, buffer_size);
              if(count==-1)
                  break;
              os.write(bytes, 0, count);
            }
        }
        catch(Exception ex){}
    }
}

Note:
Don’t forget to include INTERNET and WRITE_EXTERNAL_STORAGE permission in AndroidManifest.xml


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

Output:

asynchronous image loading in android

Download this example from here: https://github.com/PareshMayani/Android-Asynchronous-Image-Loading-Example

All the kudos goes to Fedor Vlasov for providing library for Lazy Loading Images.

Paresh Mayani

Paresh Mayani is a lead android developer from India. Currently, He is Sr. Software engineer at InfoStretch Solutions Pvt. Ltd. He has been developing android apps since very 1st version i.e. Android 1.5, he says he has seen days and nights of Android history. He is the Head/Organizer of Google Developers Group (GDG), Ahmedabad

  • http://twitter.com/iGlen_ glen

    Thanks buddy worked perfectly

    • http://www.technotalkative.com/ Paresh Mayani

      Welcome and i am glad :)

  • Liz
  • sandy

    Thanks for sharing valuable example

    • http://www.technotalkative.com/ Paresh Mayani

      Welcome :)

  • felippe

    I do see this with asyctask …
    hahaha so it is very easy

    • http://www.technotalkative.com/ Paresh Mayani

      Thats great. What about caching and loading images for next time from those cached image?

      • felippe

        yes..

  • felippe

    could make an article by doing a gallery with web images, and cache using AsyncTask

  • felippe

    I could make a gallery using his concept.
    More images are coming ruin, where the quality edict that they will show up, I did not find it in the code.

    • http://www.technotalkative.com/ Paresh Mayani

      FYI, you can increase image quality that you want. Check REQUIRED_SIZE in above ImageLoader.java class, right now its 70,just try to give 110 or 150 and check the quality.

  • felippe

    thank you for the example
    perfect
    it works for pdf files too?
    I want to save the pdf file in cache and then sends it via email.

    thanks again

  • http://www.facebook.com/peria.abirami Peria Abirami

    Hi Paresh,

    Please suggest how to add progressbar to ImageLoader to show progressbar while image loading in image view….

  • Xan

    I have problem with this lazyloader. In my nexus S first image don’t shown. All other runs perfectly. In my filecache directory i have empty file (0 bytes). Any solution for it?

  • sampath kumar

    Thanks buddy, I got here exactly what i was looking …..

    • http://www.technotalkative.com/ Paresh Mayani

      Thanks for the feedback and I am glad. Enjoy :)

  • Thanh

    Thanks :X

  • Anil

    Sir,

    pls help in my image loader it take few sec to load the image ,on that time i need to display the progress bar on middle of the image view ,pls help me

  • Sravan Kumar

    Hi Paresh,

    Download link provided in the post is not working. Could you please provide me working link. Thanks for your help.

  • Fabrício Arantes

    Hello,
    How would I get images from drawable folder using a url?

    Thanks

  • Guest

    Hi sir,
    I need this code snippet bt your download link is bounded.

  • Gopal Savaliya

    Hi sir,
    I need this code snippet bt your download page is not available.

    • http://www.technotalkative.com/ Paresh Mayani

      Hey Gopal, yes link is not working, will be uploading example again sometimes later. Requesting you to create an exmaple by your own with the code given above.

  • ur86

    Thank you very much!

  • Genevieve Caña

    how to get the image url from php? where would i insert it?

  • Rupesh Choudhary

    hi please post a list view example in which we can change the location of list item using drag and drop.

  • bibhu

    i want those imageurl string will be filled by json parsed data.
    private String imageUrls[] = {
    “https://si0.twimg.com/profile_images/2053165008/ac-new_bigger.jpg”,
    “https://si0.twimg.com/profile_images/1751120073/green-android-rotate-02__1__copy_9_bigger.jpeg”,
    “https://si0.twimg.com/profile_images/2508170683/m8jf0po4imu8t5eemjdd_bigger.png”,
    “https://si0.twimg.com/profile_images/1517737798/aam-twitter-right-final_bigger.png”,
    “https://si0.twimg.com/profile_images/1265264136/twitter_bg3_bigger.png”,
    “https://si0.twimg.com/profile_images/64827025/android-wallpaper6_2560x160_bigger.png”,
    “https://si0.twimg.com/profile_images/839219643/gals_twitter_bigger.png”,
    “https://si0.twimg.com/profile_images/2244328948/ADC4_Twitter_128_bigger.jpg”,
    “https://si0.twimg.com/profile_images/956404323/androinica-avatar_bigger.png”,
    “https://si0.twimg.com/profile_images/1417650153/android-hug_bigger.png”,

    • http://www.technotalkative.com/ Paresh Mayani

      You can parse the JSON response and can prepare an Array or an ArrayList and can pass it to your adapter.

      • bibhu

        thanku sir for your reply i already parse the url and store in an array list and assign to the string array but i stucked in this part of code.

        public static class Extra {
        public static final String IMAGES = “com.nostra13.example.universalimageloader.IMAGES”;
        public static final String IMAGE_POSITION = “com.nostra13.example.universalimageloader.IMAGE_POSITION”;
        }
        actually what is the use of this code.