Android 三级缓存Cache(内存,文件,网络)图片缓存设计

1.简介      


   大家都知道,在我们Android 开发的过程中,对于图片的处理,是非常重要的,而对于我们如果每次都重网络去拉去图片,那样会造成,现在android应用中不可避免的要使用图片,有些图片是可以变化的,需要每次启动时从网络拉取,这种场景在有广告位的应用以及纯图片应用(比如淘宝,qq的照片墙)中比较多。


现在有一个问题:假如每次启动的时候都从网络拉取图片的话,势必会消耗很多流量。在当前的状况下,对于非wifi用户来说,流量还是很贵的,一个很耗流量的应用,其用户数量级肯定要受到影响。当然,我想,向百度美拍这样的应用,必然也有其内部的图片缓存策略。总之,图片缓存是很重要而且是必须的。 



2.图片缓存的原理 


         实现图片缓存也不难,需要有相应的cache策略。这里我采用 内存-文件-网络 三层cache机制,其中内存缓存包括强引用缓存和软引用缓存(SoftReference),其实网络不算cache,这里姑且也把它划到缓存的层次结构中。当根据url向网络拉取图片的时候,先从内存中找,如果内存中没有,再从缓存文件中查找,如果缓存文件中也没有,再从网络上通过http请求拉取图片。在键值对(key-value)中,这个图片缓存的key是图片url的hash值,value就是bitmap。所以,按照这个逻辑,只要一个url被下载过,其图片就被缓存起来了。 


关于Java中对象的软引用(SoftReference),如果一个对象具有软引用,内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高 速缓存。使用软引用能防止内存泄露,增强程序的健壮性。 


3.实例源码

 

(1)内存缓存

 

[java] view plain copy
 
  1. package com.zengtao.tools;  
  2.   
  3. import java.lang.ref.SoftReference;  
  4. import java.util.LinkedHashMap;  
  5.   
  6. import android.annotation.SuppressLint;  
  7. import android.app.ActivityManager;  
  8. import android.content.Context;  
  9. import android.graphics.Bitmap;  
  10. import android.util.LruCache;  
  11.   
  12. /** 
  13.  * 内存缓存:两层缓存 
  14.  *  
  15.  * @author zengtao 2015年4月27日 上午10:39:23 
  16.  */  
  17. public class MemoryCache {  
  18.   
  19.     private final static int SOFT_CACHE_SIZE = 15; // 软引用缓存容量  
  20.     private static LruCache<String, Bitmap> mLruCache; // 硬引用缓存  
  21.     private static LinkedHashMap<String, SoftReference<Bitmap>> mSoftCache; // 软引用缓存  
  22.   
  23.     @SuppressLint("NewApi")  
  24.     public MemoryCache(Context context) {  
  25.         int memClass = ((ActivityManager) context  
  26.                 .getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();  
  27.         int cacheSize = 1024 * 1024 * memClass / 4; // 获取系统的1/4的空间 作为缓存大小  
  28.         mLruCache = new LruCache<String, Bitmap>(cacheSize) {  
  29.   
  30.             @Override  
  31.             protected int sizeOf(String key, Bitmap value) {  
  32.                 if (value != null) {  
  33.                     return value.getRowBytes() * value.getHeight();  
  34.                 }  
  35.                 return 0;  
  36.             }  
  37.   
  38.             @Override  
  39.             protected void entryRemoved(boolean evicted, String key,  
  40.                     Bitmap oldValue, Bitmap newValue) {  
  41.                 if (oldValue != null) {  
  42.                     // 硬引用缓存满的时候,会根据lru算法把最近没有被使用的图片抓入软引用  
  43.                     mSoftCache.put(key, new SoftReference<Bitmap>(oldValue));  
  44.                 }  
  45.             }  
  46.         };  
  47.         mSoftCache = new LinkedHashMap<String, SoftReference<Bitmap>>(  
  48.                 SOFT_CACHE_SIZE, 0.75f, true) {  
  49.   
  50.             private static final long serialVersionUID = 1L;  
  51.   
  52.             @Override  
  53.             protected boolean removeEldestEntry(  
  54.                     java.util.Map.Entry<String, SoftReference<Bitmap>> eldest) {  
  55.                 if (size() > SOFT_CACHE_SIZE) {  
  56.                     return true;  
  57.                 }  
  58.                 return false;  
  59.             }  
  60.         };  
  61.     }  
  62.   
  63.     /** 
  64.      * 存储图片到缓存 
  65.      *  
  66.      * @param url 
  67.      *            :key 
  68.      * @param bitmap 
  69.      *            : 图片 
  70.      */  
  71.     @SuppressLint("NewApi")  
  72.     public void saveBitmap(String url, Bitmap bitmap) {  
  73.         if (bitmap != null) {  
  74.             synchronized (bitmap) {  
  75.                 mLruCache.put(url, bitmap);  
  76.             }  
  77.         }  
  78.     }  
  79.   
  80.     /** 
  81.      * 获取缓存图片 
  82.      *  
  83.      * @param url 
  84.      *            :url 
  85.      * @return 
  86.      */  
  87.     @SuppressLint("NewApi")  
  88.     public Bitmap getBitmap(String url) {  
  89.         Bitmap bitmap = null;  
  90.         // 从硬引用找  
  91.         synchronized (mLruCache) {  
  92.             // 从硬引用中获取  
  93.             bitmap = mLruCache.get(url);  
  94.             if (bitmap != null) {  
  95.                 // 如果找到了,将元素移动到linkendHashMap的最前面,从而保证lrd算法中的是最后删除  
  96.                 mLruCache.remove(url);  
  97.                 mLruCache.put(url, bitmap);  
  98.                 return bitmap;  
  99.             }  
  100.         }  
  101.         // 硬引用没找到,从软引用找  
  102.         synchronized (mSoftCache) {  
  103.             SoftReference<Bitmap> softReference = mSoftCache.get(url);  
  104.             if (softReference != null) {  
  105.                 bitmap = softReference.get();  
  106.                 // 如果找到了,重新添加到硬缓存中  
  107.                 mLruCache.put(url, bitmap);  
  108.                 mSoftCache.remove(url);  
  109.                 return bitmap;  
  110.             } else {  
  111.                 mSoftCache.remove(url);  
  112.             }  
  113.         }  
  114.         return null;  
  115.     }  
  116.   
  117.     public void clearCache() {  
  118.         mSoftCache.clear();  
  119.     }  
  120. }  

 

 


(2)文件缓存

 

[java] view plain copy
 
  1. package com.zengtao.tools;  
  2.   
  3. import java.io.File;  
  4. import java.io.FileOutputStream;  
  5. import java.io.OutputStream;  
  6. import java.util.Arrays;  
  7. import java.util.Comparator;  
  8.   
  9. import android.annotation.SuppressLint;  
  10. import android.graphics.Bitmap;  
  11. import android.graphics.BitmapFactory;  
  12. import android.os.Environment;  
  13. import android.os.StatFs;  
  14.   
  15. /** 
  16.  * 文件缓存 
  17.  *  
  18.  * @author zengtao 2015年4月27日 上午11:49:52 
  19.  */  
  20. public class FileCache {  
  21.     private final static String IMAGECACHE = "ImageCache";  
  22.     private final static String lASTPATHNAME = ".cache"; // 文件名  
  23.   
  24.     private final static int MB = 1024 * 1024;  
  25.     private final static int CACHESIZE = 10;  
  26.     private final static int SDCARD_FREE_SPANCE_CACHE = 10;  
  27.   
  28.     public FileCache() {  
  29.         removeCache(getDirectory());  
  30.     }  
  31.   
  32.     /** 
  33.      * 将图片存入缓存 
  34.      *  
  35.      * @param url 
  36.      *            : 地址 
  37.      * @param bitmap 
  38.      *            : 图片 
  39.      */  
  40.     public void saveBitmap(String url, Bitmap bitmap) {  
  41.         if (bitmap == null) {  
  42.             return;  
  43.         }  
  44.         if (SDCARD_FREE_SPANCE_CACHE > caluateSDCardFreeSpance()) {  
  45.             return; // 空间不足  
  46.         }  
  47.         String fileName = convertUrlToFileName(url);  
  48.         String dirPath = getDirectory();  
  49.         File dirFile = new File(dirPath);  
  50.         if (dirFile.exists()) {  
  51.             dirFile.mkdirs();  
  52.         }  
  53.         File file = new File(dirPath + "/" + fileName);  
  54.         try {  
  55.             file.createNewFile();  
  56.             OutputStream outputStream = new FileOutputStream(file);  
  57.             bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);  
  58.             outputStream.flush();  
  59.             outputStream.close();  
  60.         } catch (Exception e) {  
  61.             System.out.println("文件未找到或者io异常");  
  62.         }  
  63.     }  
  64.   
  65.     /** 
  66.      * 获取文件缓存图片 
  67.      *  
  68.      * @param url 
  69.      *            : 地址 
  70.      * @return : bitmap 
  71.      */  
  72.     public Bitmap getBitmap(final String url) {  
  73.         Bitmap bitmap = null;  
  74.         final String path = getDirectory() + convertUrlToFileName(url);  
  75.         File file = new File(path);  
  76.         if (file.exists()) {  
  77.             bitmap = BitmapFactory.decodeFile(path);  
  78.             if (bitmap == null) {  
  79.                 file.delete();  
  80.             } else {  
  81.                 updateFileTime(path);  
  82.             }  
  83.         }  
  84.         return bitmap;  
  85.     }  
  86.   
  87.     /** 
  88.      * 获取sdCard路径 
  89.      *  
  90.      * @return :路径地址 
  91.      */  
  92.     private String getSDCardPath() {  
  93.         String path = "";  
  94.         File file = null;  
  95.         boolean isSDCardExist = Environment.getExternalStorageState()  
  96.                 .toString().equals(android.os.Environment.MEDIA_MOUNTED); // 判断是否有sdCard  
  97.         if (isSDCardExist) {  
  98.             file = Environment.getExternalStorageDirectory();  
  99.         }  
  100.         if (file != null) {  
  101.             path = file.toString();  
  102.         }  
  103.         return path;  
  104.     }  
  105.   
  106.     /** 
  107.      * 计算存储目录下的文件大小, 
  108.      * 当文件总大小大于规定的CACHE_SIZE或者sdcard剩余空间小于FREE_SD_SPACE_NEEDED_TO_CACHE的规定 
  109.      * 那么删除40%最近没有被使用的文件 
  110.      */  
  111.     private boolean removeCache(String dirPath) {  
  112.         File dir = new File(dirPath);  
  113.         File[] files = dir.listFiles();  
  114.         if (files == null) {  
  115.             return true;  
  116.         }  
  117.   
  118.         if (!android.os.Environment.getExternalStorageState().equals(  
  119.                 android.os.Environment.MEDIA_MOUNTED)) {  
  120.             return false;  
  121.         }  
  122.   
  123.         int dirSize = 0;  
  124.         for (int i = 0; i < files.length; i++) {  
  125.             if (files[i].getName().contains(lASTPATHNAME)) {  
  126.                 dirSize += files[i].length();  
  127.             }  
  128.         }  
  129.   
  130.         if (dirSize > CACHESIZE * MB  
  131.                 || SDCARD_FREE_SPANCE_CACHE > caluateSDCardFreeSpance()) {  
  132.             int removeFactor = (int) ((0.4 * files.length) + 1);  
  133.             Arrays.sort(files, new FileLastModifSort());  
  134.             for (int i = 0; i < removeFactor; i++) {  
  135.                 if (files[i].getName().contains(lASTPATHNAME)) {  
  136.                     files[i].delete();  
  137.                 }  
  138.             }  
  139.         }  
  140.   
  141.         if (caluateSDCardFreeSpance() <= CACHESIZE) {  
  142.             return false;  
  143.         }  
  144.   
  145.         return true;  
  146.     }  
  147.   
  148.     /** 
  149.      * 获取缓存目录 
  150.      *  
  151.      * @return : 目录 
  152.      */  
  153.     private String getDirectory() {  
  154.         return getSDCardPath() + "/" + IMAGECACHE;  
  155.     }  
  156.   
  157.     /** 
  158.      * 将url转换成文件名 
  159.      *  
  160.      * @param url 
  161.      *            : 地址 
  162.      * @return : 文件名 
  163.      */  
  164.     private String convertUrlToFileName(final String url) {  
  165.         String[] strs = url.split("/");  
  166.         return strs[strs.length - 1] + lASTPATHNAME;  
  167.     }  
  168.   
  169.     /** 
  170.      * 计算sdCard上的空闲空间 
  171.      *  
  172.      * @return : 大小 
  173.      */  
  174.     @SuppressLint("NewApi")  
  175.     private int caluateSDCardFreeSpance() {  
  176.         int freespance = 0;  
  177.         StatFs start = new StatFs(Environment.getExternalStorageDirectory()  
  178.                 .getPath());  
  179.         long blocksize = start.getBlockSizeLong();  
  180.         long availableBlocks = start.getAvailableBlocksLong();  
  181.         freespance = Integer.parseInt(blocksize * availableBlocks + "");  
  182.         return freespance;  
  183.     }  
  184.   
  185.     /** 修改文件的最后修改时间 **/  
  186.     public void updateFileTime(String path) {  
  187.         File file = new File(path);  
  188.         long lastTime = System.currentTimeMillis();  
  189.         file.setLastModified(lastTime);  
  190.     }  
  191.   
  192.     /** 根据文件的最后修改时间进行排序 **/  
  193.     private class FileLastModifSort implements Comparator<File> {  
  194.         public int compare(File arg0, File arg1) {  
  195.             if (arg0.lastModified() > arg1.lastModified()) {  
  196.                 return 1;  
  197.             } else if (arg0.lastModified() == arg1.lastModified()) {  
  198.                 return 0;  
  199.             } else {  
  200.                 return -1;  
  201.             }  
  202.         }  
  203.     }  
  204. }  

 

 


(3)http缓存

 

[java] view plain copy
 
  1. package com.zengtao.tools;  
  2.   
  3. import java.io.FilterInputStream;  
  4. import java.io.IOException;  
  5. import java.io.InputStream;  
  6.   
  7. import org.apache.http.HttpEntity;  
  8. import org.apache.http.HttpResponse;  
  9. import org.apache.http.HttpStatus;  
  10. import org.apache.http.client.HttpClient;  
  11. import org.apache.http.client.methods.HttpGet;  
  12. import org.apache.http.impl.client.DefaultHttpClient;  
  13.   
  14. import android.graphics.Bitmap;  
  15. import android.graphics.BitmapFactory;  
  16. import android.util.Log;  
  17.   
  18. /** 
  19.  * 网络缓存 
  20.  *  
  21.  * @author zengtao 2015年4月27日 下午3:32:34 
  22.  */  
  23. public class HttpCache {  
  24.     private static final String LOG_TAG = "ImageGetFromHttp";  
  25.   
  26.     public static Bitmap downloadBitmap(String url) {  
  27.         final HttpClient client = new DefaultHttpClient();  
  28.         final HttpGet getRequest = new HttpGet(url);  
  29.   
  30.         try {  
  31.             HttpResponse response = client.execute(getRequest);  
  32.             final int statusCode = response.getStatusLine().getStatusCode();  
  33.             if (statusCode != HttpStatus.SC_OK) {  
  34.                 Log.w(LOG_TAG, "Error " + statusCode  
  35.                         + " while retrieving bitmap from " + url);  
  36.                 return null;  
  37.             }  
  38.   
  39.             final HttpEntity entity = response.getEntity();  
  40.             if (entity != null) {  
  41.                 InputStream inputStream = null;  
  42.                 try {  
  43.                     inputStream = entity.getContent();  
  44.                     FilterInputStream fit = new FlushedInputStream(inputStream);  
  45.                     return BitmapFactory.decodeStream(fit);  
  46.                 } finally {  
  47.                     if (inputStream != null) {  
  48.                         inputStream.close();  
  49.                         inputStream = null;  
  50.                     }  
  51.                     entity.consumeContent();  
  52.                 }  
  53.             }  
  54.         } catch (IOException e) {  
  55.             getRequest.abort();  
  56.             Log.w(LOG_TAG, "I/O error while retrieving bitmap from " + url, e);  
  57.         } catch (IllegalStateException e) {  
  58.             getRequest.abort();  
  59.             Log.w(LOG_TAG, "Incorrect URL: " + url);  
  60.         } catch (Exception e) {  
  61.             getRequest.abort();  
  62.             Log.w(LOG_TAG, "Error while retrieving bitmap from " + url, e);  
  63.         } finally {  
  64.             client.getConnectionManager().shutdown();  
  65.         }  
  66.         return null;  
  67.     }  
  68.   
  69.     /** 
  70.      * InputStream流有个小bug在慢速网络的情况下可能产生中断,可以考虑重写FilterInputStream处理skip方法来解决这个bug 
  71.      * BitmapFactory类的decodeStream方法在网络超时或较慢的时候无法获取完整的数据,这里我 
  72.      * 们通过继承FilterInputStream类的skip方法来强制实现flush流中的数据 
  73.      * ,主要原理就是检查是否到文件末端,告诉http类是否继续。 
  74.      *  
  75.      * @author zengtao 2015年4月27日 下午6:33:17 
  76.      */  
  77.     static class FlushedInputStream extends FilterInputStream {  
  78.         public FlushedInputStream(InputStream inputStream) {  
  79.             super(inputStream);  
  80.         }  
  81.   
  82.         @Override  
  83.         public long skip(long n) throws IOException {  
  84.             long totalBytesSkipped = 0L;  
  85.             while (totalBytesSkipped < n) {  
  86.                 long bytesSkipped = in.skip(n - totalBytesSkipped);  
  87.                 if (bytesSkipped == 0L) {  
  88.                     int b = read();  
  89.                     if (b < 0) {  
  90.                         break; // we reached EOF  
  91.                     } else {  
  92.                         bytesSkipped = 1; // we read one byte  
  93.                     }  
  94.                 }  
  95.                 totalBytesSkipped += bytesSkipped;  
  96.             }  
  97.             return totalBytesSkipped;  
  98.         }  
  99.     }  
  100. }  


(4)主函数中调用

 

 

[java] view plain copy
 
  1. @SuppressLint("HandlerLeak")  
  2.     private Handler handler = new Handler() {  
  3.         public void handleMessage(Message msg) {  
  4.             if (msg.arg1 == 0x1) {  
  5.                 if (msg.obj != null) {  
  6.                     image.setImageBitmap((Bitmap) msg.obj);  
  7.                 }  
  8.             }  
  9.         };  
  10.     };  
  11.   
  12.     class MyThread extends Thread {  
  13.         @Override  
  14.         public void run() {  
  15.             bitmap = getBitmap("http://a.hiphotos.baidu.com/image/pic/item/77c6a7efce1b9d16f5022b7ef1deb48f8d5464e3.jpg");  
  16.             Message message = new Message();  
  17.             message.arg1 = 0x1;  
  18.             message.obj = bitmap;  
  19.             handler.sendMessage(message);  
  20.         }  
  21.     }  
  22.   
  23.     /*** 获得一张图片,从三个地方获取,首先是内存缓存,然后是文件缓存,最后从网络获取 ***/  
  24.     public Bitmap getBitmap(String url) {  
  25.         // 1.从内存缓存中获取图片  
  26.         Bitmap resultBitmap = memoryCache.getBitmap(url);  
  27.         if (resultBitmap == null) {  
  28.             // 2.文件缓存中获取  
  29.             resultBitmap = fileCache.getBitmap(url);  
  30.             if (resultBitmap == null) {  
  31.                 // 3.从网络获取  
  32.                 resultBitmap = HttpCache.downloadBitmap(url);  
  33.                 if (resultBitmap != null) {  
  34.                     fileCache.saveBitmap(url, resultBitmap);  
  35.                     memoryCache.saveBitmap(url, resultBitmap);  
  36.                     System.out.println("3.网络缓存中获取图片");  
  37.                 }  
  38.             } else {  
  39.                 // 添加到内存缓存  
  40.                 memoryCache.saveBitmap(url, resultBitmap);  
  41.                 System.out.println("2.文件缓存中获取图片");  
  42.             }  
  43.         } else {  
  44.             System.out.println("1.内存缓存中获取图片");  
  45.         }  
  46.         return resultBitmap;  
  47.     }  


4.总结

 

以上就完成了一套缓存的设计,值得注意的是,当去网络获取图片的时候,图片过于庞大,一定要做去异步线程中获取图片,或者做本地缓存,这样不会让用户感觉自己的app卡死,是的用户体验效果更加。

posted @ 2017-03-12 19:09  天涯海角路  阅读(639)  评论(1)    收藏  举报