Android – Asynchronous image loading in ListView

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

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

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.

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

<?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
UsingDisplayImage(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 createTTImages_cachefolder 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:

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

All the kudos goes toFedor Vlasovfor providing library for Lazy Loading Images.

About Paresh Mayani

I'm Paresh Mayani, a passionate mobile application developer from India. Having been involved in android app development since 2010. By passion, I am Head/Organizer of Google Developers Group (GDG), Ahmedabad

更多相关文章

  1. 代码中设置drawableleft
  2. android 3.0 隐藏 系统标题栏
  3. Android开发中activity切换动画的实现
  4. Android(安卓)学习 笔记_05. 文件下载
  5. Android中直播视频技术探究之—摄像头Camera视频源数据采集解析
  6. 技术博客汇总
  7. android 2.3 wifi (一)
  8. AndRoid Notification的清空和修改
  9. Android中的Chronometer

随机推荐

  1. 换个角度看硅谷的前世今生三部曲之3- 展
  2. 印度硅谷?少来了,硅谷不是你想叫,想叫就能
  3. 面向目标 vs. 面向功能: 选择正确的Produ
  4. 三分钟带你读懂硅谷的过去、现在、和未来
  5. 海外最新的垂直市场水平化攻略
  6. 创新大师Steve Blank: 你真的知道什么是
  7. 公司拿钱要3P|其实,一开始我是拒绝的...
  8. 一场VC和PE的博弈
  9. 天欲亡我,非战之罪!怪吾创业前未阅《三国》
  10. 全都是泡沫?勿把“晚轮”错当IPO!