打造完美的ImageLoader——LruCache+DiskLruCache

Android应用少不了要和网络打交道,在我刚开始学android的时候总是处理不好网络图片的加载,尤其是图片乱跳的问题,后来发现了各种缓存图片的方法:本地缓存、软引用、LruCache....

我知道的这三种方法中,第一中和其他两种并不冲突,我们完全可以缓存到本地一份,在缓存到内存中一份。软引用这样方式,第一次使用软引用的时候,感觉做一个完美的内存缓存太容易了,可惜在android2.3以后android加强了对软引用的回收,这种方式基本上算是废了。

 

LruCache登场

软引用废了,难道就没有替代品了吗? 有,android sdk中google官方添加了LruCache类, 该类使用Lru算法实现内存缓存,关于LruCache的使用,可以google一下,很简单。

 

本地缓存方案

以前我的本地缓存方案都是自己写流实现的,这种方式完全可以,而且也不是很费劲,现在也可以使用这种方式。但是,有一个更好用的方式:DiskLruCache,广域DiskLruCache,还是google一下就ok。这里说明一下,DiskLruCache并没有在SDK中需要我们自己下载,可以使用我在github上fork的:https://github.com/qibin0506/DiskLruCache  直接将源码copy到项目中即可。

 

ImageLoader的思路

给定一个url,我们通过这个url,先去内存中取图片,如果内存中不存在,在去本地获取,本地存在的话,再添加到内存中,不存在就去网络中下载,下载完毕后,缓存到本地和内存中。

 

实现

思路很清晰,那么现在开始跟着思路实现一下代码吧。

 

[java] view plain copy
 
  1. public class ImageLoader {  
  2.     private static final int SUCCESS = 1;  
  3.     private static final int FAILED = 0;  
  4.       
  5.     private static int sImageWidth;  
  6.       
  7.     private static Context sContext;  
  8.     private static ImageLoader sInstance;  
  9.     private static ExecutorService sThreadPool = Executors.newCachedThreadPool();  
  10.     private LruCache<String, Bitmap> mLruCache;  
  11.     private DiskLruCache mDiskCache;  
  12.       
  13.     /** 
  14.      * 初始化ImageLoader 
  15.      * @param context 
  16.      * @param width 预想的图片宽度 
  17.      */  
  18.     public static void init(Context context, int width) {  
  19.         sContext = context;  
  20.         sImageWidth = width;  
  21.     }  
  22.   
  23.     /** 
  24.      * 单例 获取ImageLoader的实例 
  25.      * @return 
  26.      */  
  27.     public synchronized static ImageLoader getInstance() {  
  28.         if(null == sInstance) {  
  29.             sInstance = new ImageLoader();  
  30.         }  
  31.         return sInstance;  
  32.     }  
  33.       
  34.     private ImageLoader() {  
  35.         // begin 初始化LruCache  
  36.         int maxMemory = (int) Runtime.getRuntime().maxMemory();  
  37.         int maxSize = maxMemory / 8;  
  38.         mLruCache = new LruCache<String, Bitmap>(maxSize) {  
  39.             @Override  
  40.             protected int sizeOf(String key, Bitmap value) {  
  41.                 return value.getByteCount();  
  42.             }  
  43.         };  
  44.         // end  
  45.           
  46.         // begin 初始化DiskLruCache  
  47.         try {  
  48.             mDiskCache = DiskLruCache.open(getCacheDir(), getAppVersion(), 1, 10*1024*1024);  
  49.         } catch (Exception e) {  
  50.             e.printStackTrace();  
  51.         }  
  52.         // end  
  53.     }  
  54.       
  55.     public void load(final String url, final OnImageListener l) {  
  56.         final Handler handler = new Handler() {  
  57.             @Override  
  58.             public void handleMessage(Message msg) {  
  59.                 switch (msg.what) {  
  60.                 case SUCCESS:  
  61.                     l.onResult((Bitmap) msg.obj);  
  62.                     break;  
  63.                 case FAILED:  
  64.                     l.onResult(null);  
  65.                     break;  
  66.                 }  
  67.             }  
  68.         };  
  69.           
  70.         // 从memory中获取  
  71.         Bitmap bmp = getFromLru(url);  
  72.         if(null != bmp) {  
  73.             System.out.println("--getFromLru");  
  74.             Message msg = handler.obtainMessage(SUCCESS, bmp);  
  75.             msg.sendToTarget();  
  76.             return;  
  77.         }  
  78.           
  79.         // 如果memory中没有  
  80.         // 从disk中获取  
  81.         bmp = getFromDisk(url);  
  82.         if(null != bmp) {  
  83.             System.out.println("---getFromDisk");  
  84.             Message msg = handler.obtainMessage(SUCCESS, bmp);  
  85.             msg.sendToTarget();  
  86.             return;  
  87.         }  
  88.           
  89.         // 如果disk中没有, 则从网络中下载  
  90.         sThreadPool.execute(new Runnable() {  
  91.             @Override  
  92.             public void run() {  
  93.                 System.out.println("----getFromNet");  
  94.                 DefaultHttpClient client = null;  
  95.                 try {  
  96.                     client = new DefaultHttpClient();  
  97.                     HttpGet get = new HttpGet(url);  
  98.                     HttpResponse response = client.execute(get);  
  99.                     if(200 == response.getStatusLine().getStatusCode()) {  
  100.                         InputStream in = response.getEntity().getContent();  
  101.                         Bitmap bmp = BitmapFactory.decodeStream(in);  
  102.                         bmp = scaleImage(bmp);  
  103.                           
  104.                         // 缓存到本地  
  105.                         addToDisk(url, bmp);  
  106.                         // 缓存到memory  
  107.                         addToLru(url, bmp);  
  108.                         Message msg = handler.obtainMessage(SUCCESS, bmp);  
  109.                         msg.sendToTarget();  
  110.                     }  
  111.                 } catch (Exception e) {  
  112.                     e.printStackTrace();  
  113.                     handler.sendEmptyMessage(FAILED);  
  114.                 } finally {  
  115.                     if(null != client) {  
  116.                         client.getConnectionManager().shutdown();  
  117.                     }  
  118.                 }  
  119.             }  
  120.         });  
  121.     }  
  122.       
  123.     // 缩放图片  
  124.     private Bitmap scaleImage(Bitmap bmp) {  
  125.         int sample = bmp.getWidth() / sImageWidth;  
  126.         int height = bmp.getHeight() / sample;  
  127.         return ThumbnailUtils.extractThumbnail(bmp, sImageWidth, height);  
  128.     }  
  129.       
  130.     // 从memory中获取  
  131.     private Bitmap getFromLru(String url) {  
  132.         return mLruCache.get(Encrypt.md5(url));  
  133.     }  
  134.       
  135.     // 添加到内存中  
  136.     private void addToLru(String url, Bitmap bmp) {  
  137.         if(getFromLru(url) == null) {  
  138.             System.out.println("++addToLru");  
  139.             mLruCache.put(Encrypt.md5(url), bmp);  
  140.         }  
  141.     }  
  142.       
  143.     // 从本地缓存获取  
  144.     private Bitmap getFromDisk(String url) {  
  145.         Bitmap bmp = null;  
  146.         try {  
  147.             DiskLruCache.Snapshot snapshot = mDiskCache.get(Encrypt.md5(url));  
  148.             InputStream in = snapshot.getInputStream(0);  
  149.             bmp = BitmapFactory.decodeStream(in);  
  150.             addToLru(url, bmp); // 这里可以断言 内存中肯定没有  
  151.             in.close();  
  152.         } catch (Exception e) {  
  153.         }  
  154.         return bmp;  
  155.     }  
  156.       
  157.     // 添加到本地缓存  
  158.     private void addToDisk(String url, Bitmap bmp) throws Exception {  
  159.         System.out.println("+++addtoDisk");  
  160.         ByteArrayOutputStream baos = new ByteArrayOutputStream();  
  161.         bmp.compress(CompressFormat.PNG, 100, baos);  
  162.         byte[] buf = baos.toByteArray();  
  163.   
  164.         DiskLruCache.Editor editor = mDiskCache.edit(Encrypt.md5(url));  
  165.         OutputStream out = editor.newOutputStream(0);  
  166.         out.write(buf);  
  167.         out.flush();  
  168.         editor.commit();  
  169.     }  
  170.       
  171.     // 获取缓存目录  
  172.     private File getCacheDir() {  
  173.         File dir = null;  
  174.         if(Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {  
  175.             dir = new File(sContext.getExternalCacheDir().getPath() + File.separator + "images" + File.separator);  
  176.         }else {  
  177.             dir = new File(sContext.getCacheDir().getPath() + File.separator + "images" + File.separator);  
  178.         }  
  179.         return dir;  
  180.     }  
  181.       
  182.     // 获取软件版本  
  183.     private int getAppVersion() {  
  184.         PackageInfo pi;  
  185.         try {  
  186.             pi = sContext.getPackageManager().getPackageInfo(sContext.getPackageName(), 0);  
  187.             return pi.versionCode;  
  188.         } catch (NameNotFoundException e) {  
  189.             e.printStackTrace();  
  190.         }  
  191.         return 0;  
  192.     }  
  193.       
  194.     public interface OnImageListener {  
  195.         public void onResult(Bitmap bmp);  
  196.     }  
  197. }  


可以看到ImageLoader中有一个init方法,该方法主要是初始化ImageLoader类中的context和我们预想的图片的宽度,为什么没有高度? 高度要根据宽度比自动计算的。

 

27~32行,获取ImageLoader的实例, 因为ImageLoader是单例的。

构造方法中,36~43行初始化LruCache,47~51行初始化DiskLruCache, DiskLruCache的静态方法open接收4个参数,分别是:缓存的路径,软件的版本号,一个key对应多少文件,最大缓存的大小。

load方法中,71~77行,从memory中尝试获取图片,如果获取到了,则发送消息,并停止执行。81~87行,如果从memory中获取不到,则尝试从本地缓存中获取,如果获取到了,则发送消息,并停止执行。94~110行,从网络下载图片,并添加到本地缓存和memory中。

scaleImage方法,是一个简单的缩放图片功能,使用了ThumbnailUtils.extractThumbnail,如果在低版本中,需要自己去实现。

131~141行,是从memory中获取和添加到memory中。

144~169行,是从本地缓存中获取和添加到本地缓存。

剩下的两个方法是获取缓存路径和软件版本号。

 

ImageLoader定义好了以后,怎么使用呢? 很简单,调用load方法就行。

首先自定义一个Adapter继承自BaseAdapter,

 

[java] view plain copy
 
  1. public class ImageAdapter extends BaseAdapter {  
  2.     private Context mContext;  
  3.     private String[] mData;  
  4.   
  5.     public ImageAdapter(Context context, String[] data) {  
  6.         mContext = context;  
  7.         mData = data;  
  8.         initImageLoader();  
  9.     }  
  10.       
  11.     private void initImageLoader() {  
  12.         WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);  
  13.         DisplayMetrics dm = new DisplayMetrics();  
  14.         wm.getDefaultDisplay().getMetrics(dm);  
  15.         ImageLoader.init(mContext, dm.widthPixels / 3);  
  16.     }  
  17.       
  18.     @Override  
  19.     public int getCount() {  
  20.         return mData.length;  
  21.     }  
  22.   
  23.     @Override  
  24.     public Object getItem(int position) {  
  25.         return mData[position];  
  26.     }  
  27.   
  28.     @Override  
  29.     public long getItemId(int position) {  
  30.         return position;  
  31.     }  
  32.   
  33.     @Override  
  34.     public View getView(int position, View convertView, ViewGroup parent) {  
  35.         final ViewHolder holder;  
  36.         if(null == convertView) {  
  37.             convertView = View.inflate(mContext, R.layout.image_item, null);  
  38.             holder = new ViewHolder();  
  39.             holder.imageView = (ImageView) convertView.findViewById(R.id.iv);  
  40.             convertView.setTag(R.id.iv, holder);  
  41.         }else {  
  42.             holder = (ViewHolder) convertView.getTag(R.id.iv);  
  43.         }  
  44.           
  45.         holder.imageView.setImageResource(R.drawable.ic_launcher);  
  46.           
  47.         ImageLoader imageLoader = ImageLoader.getInstance();  
  48.         imageLoader.load(mData[position], new ImageLoader.OnImageListener() {  
  49.             @Override  
  50.             public void onResult(Bitmap bmp) {  
  51.                 holder.imageView.setImageBitmap(bmp);  
  52.             }  
  53.         });  
  54.           
  55.         return convertView;  
  56.     }  
  57.       
  58.     class ViewHolder {  
  59.         ImageView imageView;  
  60.     }  
  61. }  

标准的BaseAdapter,需要注意到是第45行,主要是为了解决图片跳动的问题。

 

 

最后看一下效果吧:



图片借用了郭神的, 实在是懒得自己去找了。 效果还不错,挺顺畅的。

posted @ 2016-11-24 21:10  天涯海角路  阅读(80)  评论(0)    收藏  举报