Android ListView 图片异步加载和图片内存缓存

开发Android应用经常需要处理图片的加载问题。因为图片一般都是存放在服务器端,需要联网去加载,而这又是一个比较耗时的过程,所以Android中都是通过开启一个异步线程去加载。为了增加用户体验,给用户省流量,一般把加载完的图片先缓存下来,下次加载的时候就不需要再联网去服务器端加载。图片缓存一般分为一级缓存(即内存缓存)和二级缓存(即磁盘缓存)。这里只讲一级缓存。

内存缓存就是把加载完的图片先放在手机内存中,等下次加载的时候再从内存中取出来。

优点是速度快,缺点是不能长久保存,用户退出应用程序之后内存缓存就被回收了。而且加载太多会抛出Java.lang.OutOfMemory异常。

磁盘缓存就是把加载完的图片放到手机内置存储卡或SD卡中。下次加载的时候在从里面取出来。

优点是能够长期保存,缺点是速度较慢,为了不影响用户体验,也一般是通过异步线程去取磁盘中的缓存图片;还有一个就是用户卸载应用程序后这些缓存文件不会随着删除,浪费了用户的磁盘空间,需要用户手动删除。

     

图片缓存很多人都是用软引用SoftRReference来处理,但是Android官方并不推荐这么用。

Android官方给出的理由是:Note: In the past, a popular memory cache implementation was a SoftReference or WeakReference bitmap cache, however this is not recommended. Starting from Android 2.3 (API Level 9) the garbage collector is more aggressive with collecting soft/weak references which makes them fairly ineffective. In addition, prior to Android 3.0 (API Level 11), the backing data of a bitmap was stored in native memory which is not released in a predictable manner, potentially causing an application to briefly exceed its memory limits and crash.

        

所以我们一般使用LruCache这个类,这个类可以这样理解:它就是一个内存缓存对象,你的图片就缓存在这个类里面。(Java中什么都是对象)这个类是在API 12中才有的,不过幸运的是Android官方把它加入了v4包中,所以1.6以后的都可以使用这个类。这个类用起来也比较简单(官方文档上也讲的很详细)。

    

下面讲一下我是怎样用的:

首先是给ListView 自定义一个Adapter(继承自BaseAdapter),然后在这个类中申明一个成员变量LruCache ,而不能在getView()方法中申明。因为一个ListView所有的Item项只能维护这一个LruCache 。LruCache有一个构造方法LruCache(int maxSize) ,参数就是这个缓存空间最大的容量(多少字节)。申明一个LruCache对象一般需要复写它的sizeOf(K key, V value),用这个方法改变每个缓存条目计算大小的方式。Android源码中默认是返回1,说默认是以个数来计算的。(其实这里我也不太理解,有懂的朋友还望不吝赐教。)

 

Android sizeof()的源码:

[java]  view plain  copy
 print ?
  1. /** 
  2.      * Returns the size of the entry for {@code key} and {@code value} in 
  3.      * user-defined units.  The default implementation returns 1 so that size 
  4.      * is the number of entries and max size is the maximum number of entries. 
  5.      * 
  6.      * <p>An entry's size must not change while it is in the cache. 
  7.      */  
  8.     protected int sizeOf(K key, V value) {  
  9.         return 1;  
  10.     }  

不过我是参照官方文档写的。

[java]  view plain  copy
 print ?
  1. private final int maxMemory = (int) Runtime.getRuntime().maxMemory();//获取当前应用程序所分配的最大内存  
  2.     private final int cacheSize = maxMemory / 5;//只分5分之一用来做图片缓存  
  3.     private LruCache<String, Bitmap> mLruCache = new LruCache<String, Bitmap>(  
  4.             cacheSize) {  
  5.         @Override  
  6.         protected int sizeOf(String key, Bitmap bitmap) {//复写sizeof()方法  
  7.             // replaced by getByteCount() in API 12  
  8.             return bitmap.getRowBytes() * bitmap.getHeight() / 1024//这里是按多少KB来算  
  9.         }  
  10.     };  

然后写一个异步加载图片的类AsyncImageLoader ; 这个类采用AsyncTask来异步加载图片。

[java]  view plain  copy
 print ?
  1. package com.folyd.tuan.util;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import android.graphics.Bitmap;  
  6. import android.os.AsyncTask;  
  7. import android.support.v4.util.LruCache;  
  8. import android.widget.ImageView;  
  9.   
  10. import com.folyd.tuan.util.SimpleImageLoader;  
  11.   
  12. /** 
  13.  * 图片异步加载类,有图片内存缓存 
  14.  *  
  15.  * @author Folyd 
  16.  *  
  17.  */  
  18. public class AsyncImageLoader extends AsyncTask<String, Void, Bitmap> {  
  19.     private ImageView image;  
  20.     private LruCache<String, Bitmap> lruCache;  
  21.   
  22.     /** 
  23.      * 构造方法,需要把ImageView控件和LruCache 对象传进来 
  24.      * @param image 加载图片到此 {@code}ImageView 
  25.      * @param lruCache 缓存图片的对象 
  26.      */  
  27.     public AsyncImageLoader(ImageView image, LruCache<String, Bitmap> lruCache) {  
  28.         super();  
  29.         this.image = image;  
  30.         this.lruCache = lruCache;  
  31.     }  
  32.   
  33.     @Override  
  34.     protected Bitmap doInBackground(String... params) {  
  35.         Bitmap bitmap = null;  
  36.         try {  
  37.             bitmap = SimpleImageLoader.getBitmap(params[0]);  
  38.         } catch (IOException e) {  
  39.             e.printStackTrace();  
  40.         }  
  41.         addBitmapToMemoryCache(params[0], bitmap);  
  42.         return bitmap;  
  43.     }  
  44.   
  45.     @Override  
  46.     protected void onPostExecute(Bitmap bitmap) {  
  47.         image.setImageBitmap(bitmap);  
  48.     }  
  49.         //调用LruCache的put 方法将图片加入内存缓存中,要给这个图片一个key 方便下次从缓存中取出来  
  50.     private void addBitmapToMemoryCache(String key, Bitmap bitmap) {  
  51.         if (getBitmapFromMemoryCache(key) == null) {  
  52.             lruCache.put(key, bitmap);  
  53.         }  
  54.     }  
  55.         //调用Lrucache的get 方法从内存缓存中去图片  
  56.     public Bitmap getBitmapFromMemoryCache(String key) {  
  57.         return lruCache.get(key);  
  58.     }  
  59. }  

  SimpleImageLoader 的getBitmap方法是一个简单的从网上获取图片的方法。

[java]  view plain  copy
 print ?
  1. package com.folyd.tuan.util;  
  2.   
  3. import java.io.IOException;  
  4. import java.io.InputStream;  
  5. import java.net.HttpURLConnection;  
  6. import java.net.URL;  
  7.   
  8. import android.graphics.Bitmap;  
  9. import android.graphics.BitmapFactory;  
  10.   
  11. /** 
  12.  * 简单的图片加载工具类,此类没有涉及到图片缓存。 
  13.  * @author Folyd 
  14.  * 
  15.  */  
  16. public class SimpleImageLoader {  
  17.   
  18.     public static Bitmap getBitmap(String urlStr) throws IOException{  
  19.         Bitmap bitmap;  
  20.         URL url = new URL(urlStr);  
  21.         HttpURLConnection conn = (HttpURLConnection) url.openConnection();  
  22.         conn.setRequestMethod("GET");  
  23.         conn.setReadTimeout(5*1000);  
  24.         conn.setDoInput(true);  
  25.         conn.connect();  
  26.         InputStream is = conn.getInputStream();  
  27.         bitmap = BitmapFactory.decodeStream(is);  
  28.         is.close();  
  29.         return bitmap;  
  30.     }  
  31. }  


然后在自定义的Adapter中封装一个加载图片的方法。这里涉及到AsyncTask 的一些知识,可以查看官方文档学习。

[java]  view plain  copy
 print ?
  1. /** 
  2.      *  
  3.      * @param urlStr 所需要加载的图片的url,以String形式传进来,可以把这个url作为缓存图片的key 
  4.      * @param image ImageView 控件 
  5.      */  
  6.     private void loadBitmap(String urlStr, ImageView image) {  
  7.         AsyncImageLoader asyncLoader = new AsyncImageLoader(image, mLruCache);//什么一个异步图片加载对象  
  8.         Bitmap bitmap = asyncLoader.getBitmapFromMemoryCache(urlStr);//首先从内存缓存中获取图片  
  9.         if (bitmap != null) {  
  10.             image.setImageBitmap(bitmap);//如果缓存中存在这张图片则直接设置给ImageView  
  11.         } else {  
  12.             image.setImageResource(R.drawable.thum);//否则先设置成默认的图片  
  13.             asyncLoader.execute(urlStr);//然后执行异步任务AsycnTask 去网上加载图片  
  14.         }  
  15.     }  

然后在自定义Adapter的getView()方法方法中调用上面的loadBitmap()方法。


这样就实习了图片异步加载和内存缓存了。

看一下效果图:

Android ListView 图片异步加载和图片内存缓存_第1张图片                Android ListView 图片异步加载和图片内存缓存_第2张图片


当然没有磁盘缓存的ListView 绝对是不行的。ListView还有很多需要优化的技巧需要我们去不断学习。比如说图片懒加载。

你可能感兴趣的:(Android ListView 图片异步加载和图片内存缓存)