Download Images From Web And Lazy Load In ListView - Android Example

In this example downloading images from web to a listview. Using lazy loading to download images in a listview. Using custom adapter to create lisview rows and using ImageLoader class to lazy load images from web and show in listview row. Click on listview showing image url in alert.

 

Steps :

    MainActivity.java : Initialize static image url in string array and create listview.
    LazyImageLoadAdapter.java : Used to create each list row. Inflate tabitem.xml file for each row and call ImageLoader.java to download image from url and resize downloaded image cache on sdcard.
    ImageLoader.java : Used to download image from url and resize downloaded image and make file cache on sdcard. Lazy load images for listview rows.
    FileCache.java : Used to create folder at sdcard and create map to store downloaded image information.
    MemoryCache.java : Used to set cache folder size limit ( How much mb/kb downloaded image cache folder will store ) and also used to clear cache files from sdcard.
    Utils.java : Used to Create Cache image for images downloaded from web.

 

Example Flow :


Download and show image in listview

 

 

Project Structure :


Lazy load image in listview project sketch

 

File : AndroidManifest.xml

 

- Define android.permission.WRITE_EXTERNAL_STORAGE and android.permission.INTERNET permissions.

    
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.androidexample.lazyimagedownload"
      android:versionCode="1"
      android:versionName="1.0"
      debuggable="true">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name="com.androidexample.lazyimagedownload.MainActivity" 
                  android:label="@string/app_name"
                  android:configChanges="keyboardHidden|orientation">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>
    <uses-sdk android:minSdkVersion="8"  />
    <uses-permission android:name="android.permission.INTERNET"/>    
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
</manifest> 
 

 

 

File : main.xml

 

- This file used to show main activity screen.
- Define ListView in this file.

    
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <ListView
        android:id="@+id/list"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" 
        android:layout_weight="1"/>
    <Button
        android:id="@+id/button1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" 
        android:text="Refresh cache"/>
</LinearLayout>
 

 

File : MainActivity.java

 

- Define number of image urls in a string array.
- Call adapter class to create listview rows.

 

package com.androidexample.lazyimagedownload;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;

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

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        list=(ListView)findViewById(R.id.list);
        
        // Create custom adapter for listview
        adapter=new LazyImageLoadAdapter(this, mStrings);
        
        //Set adapter to listview
        list.setAdapter(adapter);
        
        Button b=(Button)findViewById(R.id.button1);
        b.setOnClickListener(listener);
    }
    
    @Override
    public void onDestroy()
    {
    	// Remove adapter refference from list
        list.setAdapter(null);
        super.onDestroy();
    }
    
    public OnClickListener listener=new OnClickListener(){
        @Override
        public void onClick(View arg0) {
        	
        	//Refresh cache directory downloaded images
            adapter.imageLoader.clearCache();
            adapter.notifyDataSetChanged();
        }
    };
    
    
    public void onItemClick(int mPosition)
    {
    	String tempValues = mStrings[mPosition];
    	
    	Toast.makeText(MainActivity.this, 
    			"Image URL : "+tempValues, 
    			Toast.LENGTH_LONG).show();
    }
    
    // Image urls used in LazyImageLoadAdapter.java file
    
    private String[] mStrings={
            "/media/webservice/LazyListView_images/image0.png",
            "/media/webservice/LazyListView_images/image1.png",
            "/media/webservice/LazyListView_images/image2.png",
            "/media/webservice/LazyListView_images/image3.png",
            "/media/webservice/LazyListView_images/image4.png",
            "/media/webservice/LazyListView_images/image5.png",
            "/media/webservice/LazyListView_images/image6.png",
            "/media/webservice/LazyListView_images/image7.png",
            "/media/webservice/LazyListView_images/image8.png",
            "/media/webservice/LazyListView_images/image9.png",
            "/media/webservice/LazyListView_images/image10.png",
            "/media/webservice/LazyListView_images/image0.png",
            "/media/webservice/LazyListView_images/image1.png",
            "/media/webservice/LazyListView_images/image2.png",
            "/media/webservice/LazyListView_images/image3.png",
            "/media/webservice/LazyListView_images/image4.png",
            "/media/webservice/LazyListView_images/image5.png",
            "/media/webservice/LazyListView_images/image6.png",
            "/media/webservice/LazyListView_images/image7.png",
            "/media/webservice/LazyListView_images/image8.png",
            "/media/webservice/LazyListView_images/image9.png",
            "/media/webservice/LazyListView_images/image10.png",
            "/media/webservice/LazyListView_images/image0.png",
            "/media/webservice/LazyListView_images/image1.png",
            "/media/webservice/LazyListView_images/image2.png",
            "/media/webservice/LazyListView_images/image3.png",
            "/media/webservice/LazyListView_images/image4.png",
            "/media/webservice/LazyListView_images/image5.png",
            "/media/webservice/LazyListView_images/image6.png",
            "/media/webservice/LazyListView_images/image7.png",
            "/media/webservice/LazyListView_images/image8.png",
            "/media/webservice/LazyListView_images/image9.png",
            "/media/webservice/LazyListView_images/image10.png"
            
    };
}


 

 

File : LazyImageLoadAdapter.java

 

- Create custom adapter.
- Inflate tabitem.xml file to create rows in listview.
- Call ImageLoader.java to download and cache images from web.

 

package com.androidexample.lazyimagedownload;

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

//Adapter class extends with BaseAdapter and implements with OnClickListener 
public class LazyImageLoadAdapter extends BaseAdapter implements OnClickListener{
    
    private Activity activity;
    private String[] data;
    private static LayoutInflater inflater=null;
    public ImageLoader imageLoader; 
    
    public LazyImageLoadAdapter(Activity a, String[] d) {
        activity = a;
        data=d;
        inflater = (LayoutInflater)activity.
                            getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        
        // Create ImageLoader object to download and show image in list
        // Call ImageLoader constructor to initialize FileCache
        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;
    }
    
    /********* Create a holder Class to contain inflated xml file elements *********/
    public static class ViewHolder{
         
        public TextView text;
        public TextView text1;
        public TextView textWide;
        public ImageView image;
 
    }
    
    public View getView(int position, View convertView, ViewGroup parent) {
    	
        View vi=convertView;
        ViewHolder holder;
         
        if(convertView==null){
             
            /****** Inflate tabitem.xml file for each row ( Defined below ) *******/
            vi = inflater.inflate(R.layout.listview_row, null);
             
            /****** View Holder Object to contain tabitem.xml file elements ******/

            holder = new ViewHolder();
            holder.text = (TextView) vi.findViewById(R.id.text);
            holder.text1=(TextView)vi.findViewById(R.id.text1);
            holder.image=(ImageView)vi.findViewById(R.id.image);
             
           /************  Set holder with LayoutInflater ************/
            vi.setTag( holder );
        }
        else 
            holder=(ViewHolder)vi.getTag();
        
        
        holder.text.setText("Company "+position);
        holder.text1.setText("company description "+position);
        ImageView image = holder.image;
        
        //DisplayImage function from ImageLoader Class
        imageLoader.DisplayImage(data[position], image);
        
        /******** Set Item Click Listner for LayoutInflater for each row ***********/
        vi.setOnClickListener(new OnItemClickListener(position));
        return vi;
    }

	@Override
	public void onClick(View arg0) {
		// TODO Auto-generated method stub
		
	}
	
    
    /********* Called when Item click in ListView ************/
    private class OnItemClickListener  implements OnClickListener{           
        private int mPosition;
        
       OnItemClickListener(int position){
        	 mPosition = position;
        }
        
        @Override
        public void onClick(View arg0) {
        	MainActivity sct = (MainActivity)activity;
        	sct.onItemClick(mPosition);
        }               
    }   
}

 

File : File : listview_row.xml

 

 
<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="fill_parent"
          android:layout_height="wrap_content"
          android:background="@drawable/bar_bg_thin"
           android:paddingTop="0dip" android:layout_gravity="top"
         >
        <TableRow >
            <LinearLayout 
                  android:layout_width="70dp"
                  android:layout_height="match_parent"
                  android:paddingLeft="10dp"
                  android:paddingTop="5dp"
                  android:paddingBottom="5dp"
                  >
                <ImageView
                 android:layout_gravity="left|top" 
                  android:scaleType="centerCrop"
                  android:id="@+id/image"
                  android:src="@drawable/stub"
                   android:layout_width="50dip" 
	              android:layout_height="50dip"
                  />
              </LinearLayout>
  <TableLayout 
          android:layout_width="fill_parent"
          android:layout_height="wrap_content"
          android:paddingTop="0dip" android:layout_gravity="top"
            >
            <TableRow>       
               <TextView
                    android:id="@+id/text"
                    android:layout_height="wrap_content"
                    android:layout_width="wrap_content"
                    android:layout_weight="1" android:layout_gravity="left|center_vertical"
                    android:textSize="14sp"
                    android:layout_marginLeft="10dip"
                    android:layout_marginTop="2dip"
                    android:textColor="#000000"
                    android:layout_span="1"
                    />
              </TableRow>
              <TableRow>          
                 <TextView
                    android:text=""
                    android:id="@+id/text1"
                    android:layout_height="wrap_content"
                    android:layout_width="wrap_content"
                    android:layout_weight="1"
                    android:layout_gravity="left|center_vertical"
                    android:textSize="11sp"
                    android:textColor="#000000"
                    android:layout_marginLeft="10dip"
                    android:layout_marginTop="4dip"
                    android:gravity="left"/>
              </TableRow>
            </TableLayout>
               
               <ImageView
                  android:layout_height="30dp"
                  android:layout_width="30dp"
                  android:scaleType="centerCrop"
                  android:background="@drawable/dislike1"
                  android:layout_marginLeft="10dp"
                  android:layout_marginTop="10dp"
                  android:gravity="left"
                  />
       </TableRow>
</TableLayout>

 

File : ImageLoader.java

 

- Used to download images from web and resize.
- Lazy load images in listview.
- Further explanation See in comments.

 

package com.androidexample.lazyimagedownload;

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.os.Handler;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.widget.ImageView;

public class ImageLoader {
    
	// Initialize MemoryCache
    MemoryCache memoryCache = new MemoryCache();
    
    FileCache fileCache;
    
    //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){
    	
        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.stub;
    
    public void DisplayImage(String url, ImageView imageView)
    {
    	//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
            imageView.setImageBitmap(bitmap);
        }
        else
        {
        	//queue Photo to download from url
            queuePhoto(url, imageView);
            
            //Before downloading image show default image 
            imageView.setImageResource(stub_id);
        }
    }
        
    private void queuePhoto(String url, ImageView imageView)
    {
    	// Store image and url in PhotoToLoad object
        PhotoToLoad p = new PhotoToLoad(url, imageView);
        
        // 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 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() {
            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 {
        	
            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();
            
            // 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(is, os);
            
            os.close();
            conn.disconnect();
            
            //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(bitmap);
            else
                photoToLoad.imageView.setImageResource(stub_id);
        }
    }

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

}


 

 

File : FileCache.java

 


package com.androidexample.lazyimagedownload;

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

public class FileCache {
    
    private File cacheDir;
    
    public FileCache(Context context){
    	
        //Find the dir at SDCARD to save cached images
    	
        if (android.os.Environment.getExternalStorageState().equals(
                                     android.os.Environment.MEDIA_MOUNTED))
        {
        	//if SDCARD is mounted (SDCARD is present on device and mounted)
        	cacheDir = new File(
                       android.os.Environment.getExternalStorageDirectory(),"LazyList");
        }
        else
        {
        	// if checking on simulator the create cache dir in your application context
            cacheDir=context.getCacheDir();
        }
        
        if(!cacheDir.exists()){
        	// create cache dir in your application context
            cacheDir.mkdirs();
        }
    }
    
    public File getFile(String url){
        //Identify images by hashcode or encode by URLEncoder.encode.
        String filename=String.valueOf(url.hashCode());
        
        File f = new File(cacheDir, filename);
        return f;
        
    }
    
    public void clear(){
    	// list all files inside cache directory
        File[] files=cacheDir.listFiles();
        if(files==null)
            return;
        //delete all cache directory files
        for(File f:files)
            f.delete();
    }

}

 

 

File : MemoryCache.java

 

package com.androidexample.lazyimagedownload;

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;
            
            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){
        
            //least recently accessed item will be the first one iterated
            Iterator<Entry<String, Bitmap>> iter=cache.entrySet().iterator();
              
            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{
             // Clear cache
            cache.clear();
            size=0;
        }catch(NullPointerException ex){
            ex.printStackTrace();
        }
    }

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


 

 

File : Utils.java

 

package com.androidexample.lazyimagedownload;

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){}
    }
}