首页 > 代码库 > android 网络图片双缓存

android 网络图片双缓存

本文转自:http://blog.csdn.net/a79412906/article/details/10180583

Android每次加载图片很浪费时间。所以设计了一个图片缓存技术来解决每次android手机加载图片的问题

 

内存的读取速度是最快的,然后是文件的读取速度,最后是网络资源的读取

 

既然内存的读取时间最快,我们好好利用内存资源。将内存再分两层缓存

强引用缓存不会轻易被回收,来保存常用数据,不常用的资源放入软引用缓存中。

对于硬引用和软引用的介绍:

强引用(StrongReference)
    强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。

⑵软引用(SoftReference)

如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存

 1,对于强引用和软引用的使用,我们首先去强引用缓存中去找图片资源,当没有发现时,就去软引用缓存中。当强引用的超额时,将最后使用的资源放入软引用缓存中,使用到软引用的资源时 ,则将资源重新放回强引用缓存池中。

 2,内存缓存池中找不到,就去文件中查找,

 

3,再找不到就只好去网络上下载了,下载到以后,分别将资源放入文件缓存和内存缓存中

 

Android对于InputStream流有个小bug在慢速网络的情况下可能产生中断,可以考虑重写FilterInputStream处理skip方法来解决这个bug。 BitmapFactory类的decodeStream方法在网络超时或较慢的时候无法获取完整的数据,这里我 们通过继承FilterInputStream类的skip方法来强制实现flush流中的数据,主要原理就是检查是否到文件末端,告诉http类是否继续。

 static class FlushedInputStream extends FilterInputStream {

        public FlushedInputStream(InputStream inputStream) {

            super(inputStream);

        }

                                                       

        @Override

        public long skip(long n) throws IOException {

            long totalBytesSkipped = 0L;

            while (totalBytesSkipped < n) {

                long bytesSkipped = in.skip(n - totalBytesSkipped);

                if (bytesSkipped == 0L) {

                    int b = read();

                    if (b < 0) {

                        break;  // we reached EOF

                    } else {

                        bytesSkipped = 1; // we read one byte

                    }

                }

                totalBytesSkipped += bytesSkipped;

            }

            return totalBytesSkipped;

        }

    }

 

 主界面读取图片

[java] view plaincopy
  1. public class MainActivity extends Activity {  
  2.   
  3.     private ImageMemoryCache memoryCache;  
  4.     private ImageFileCache fileCache;  
  5.     private ImageView imageView;  
  6.     @Override  
  7.     protected void onCreate(Bundle savedInstanceState) {  
  8.         super.onCreate(savedInstanceState);  
  9.         setContentView(R.layout.main);  
  10.         memoryCache=new ImageMemoryCache(this);  
  11.         fileCache=new ImageFileCache();  
  12.         imageView=(ImageView) findViewById(R.id.img);  
  13.         Bitmap b=getBitmap("http://f.hiphotos.baidu.com/album/w%3D2048/sign=7aa167f79f2f07085f052d00dd1cb999/472309f7905298228f794c7bd6ca7bcb0b46d4c4.jpg");  
  14.         imageView.setImageBitmap(b);  
  15.       
  16.           
  17.           
  18.     }  
  19.   
  20.     public Bitmap getBitmap(String url) {  
  21.         // 从内存缓存中获取图片  
  22.         Bitmap result = memoryCache.getBitmapFromCache(url);  
  23.         if (result == null) {  
  24.             // 文件缓存中获取  
  25.             result = fileCache.getImage(url);  
  26.             if (result == null) {  
  27.                 // 从网络获取  
  28.                 result = ImageGetFromHttp.downloadBitmap(url);  
  29.                 if (result != null) {  
  30.                     fileCache.saveBitmap(result, url);  
  31.                     memoryCache.addBitmapToCache(url, result);  
  32.                 }  
  33.             } else {  
  34.                 // 添加到内存缓存  
  35.                 memoryCache.addBitmapToCache(url, result);  
  36.             }  
  37.         }  
  38.         return result;  
  39.     }  
  40.   
  41. }  

 

 内存中读取

[java] view plaincopy
  1. public class ImageMemoryCache {  
  2.     /** 
  3.      * 从内存读取数据速度是最快的,为了更大限度使用内存,这里使用了两层缓存。 
  4.      * 硬引用缓存不会轻易被回收,用来保存常用数据,不常用的转入软引用缓存。 
  5.      */  
  6.     private static final int SOFT_CACHE_SIZE = 15;  //软引用缓存容量  
  7.     private static LruCache<String, Bitmap> mLruCache;  //硬引用缓存  
  8.     private static LinkedHashMap<String, SoftReference<Bitmap>> mSoftCache;  //软引用缓存  
  9.                                                                                             
  10.     public ImageMemoryCache(Context context) {  
  11.         int memClass = ((ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();  
  12.         int cacheSize = 1024 * 1024 * memClass / 4;  //硬引用缓存容量,为系统可用内存的1/4  
  13.         mLruCache = new LruCache<String, Bitmap>(cacheSize) {  
  14.             @Override  
  15.             protected int sizeOf(String key, Bitmap value) {  
  16.                 if (value != null)  
  17.                     return value.getRowBytes() * value.getHeight();  
  18.                 else  
  19.                     return 0;  
  20.             }  
  21.                                                                                             
  22.             @Override  
  23.             protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {  
  24.                 if (oldValue != null)  
  25.                     // 硬引用缓存容量满的时候,会根据LRU算法把最近没有被使用的图片转入此软引用缓存  
  26.                     mSoftCache.put(key, new SoftReference<Bitmap>(oldValue));  
  27.             }  
  28.         };  
  29.         mSoftCache = new LinkedHashMap<String, SoftReference<Bitmap>>(SOFT_CACHE_SIZE, 0.75f, true) {  
  30.             private static final long serialVersionUID = 6040103833179403725L;  
  31.             @Override  
  32.             protected boolean removeEldestEntry(Entry<String, SoftReference<Bitmap>> eldest) {  
  33.                 if (size() > SOFT_CACHE_SIZE){      
  34.                     return true;    
  35.                 }    
  36.                 return false;   
  37.             }  
  38.         };  
  39.     }  
  40.                                                                                     
  41.     /** 
  42.      * 从缓存中获取图片 
  43.      */  
  44.     public Bitmap getBitmapFromCache(String url) {  
  45.         Bitmap bitmap;  
  46.         //先从硬引用缓存中获取  
  47.         synchronized (mLruCache) {  
  48.             bitmap = mLruCache.get(url);  
  49.             if (bitmap != null) {  
  50.                 //如果找到的话,把元素移到LinkedHashMap的最前面,从而保证在LRU算法中是最后被删除  
  51.                 mLruCache.remove(url);  
  52.                 mLruCache.put(url, bitmap);  
  53.                 return bitmap;  
  54.             }  
  55.         }  
  56.         //如果硬引用缓存中找不到,到软引用缓存中找  
  57.         synchronized (mSoftCache) {   
  58.             SoftReference<Bitmap> bitmapReference = mSoftCache.get(url);  
  59.             if (bitmapReference != null) {  
  60.                 bitmap = bitmapReference.get();  
  61.                 if (bitmap != null) {  
  62.                     //将图片移回硬缓存  
  63.                     mLruCache.put(url, bitmap);  
  64.                     mSoftCache.remove(url);  
  65.                     return bitmap;  
  66.                 } else {  
  67.                     mSoftCache.remove(url);  
  68.                 }  
  69.             }  
  70.         }  
  71.         return null;  
  72.     }   
  73.                                                                                     
  74.     /** 
  75.      * 添加图片到缓存 
  76.      */  
  77.     public void addBitmapToCache(String url, Bitmap bitmap) {  
  78.         if (bitmap != null) {  
  79.             synchronized (mLruCache) {  
  80.                 mLruCache.put(url, bitmap);  
  81.             }  
  82.         }  
  83.     }  
  84.                                                                                     
  85.     public void clearCache() {  
  86.         mSoftCache.clear();  
  87.     }  
  88. }  
[java] view plaincopy
  1. public class ImageFileCache {  
  2.     private static final String CACHDIR = "ImgCach";  
  3.     private static final String WHOLESALE_CONV = ".cach";  
  4.                                                               
  5.     private static final int MB = 1024*1024;  
  6.     private static final int CACHE_SIZE = 10;  
  7.     private static final int FREE_SD_SPACE_NEEDED_TO_CACHE = 10;  
  8.                                                                   
  9.     public ImageFileCache() {  
  10.         //清理文件缓存  
  11.         removeCache(getDirectory());  
  12.     }  
  13.                                                                   
  14.     /** 从缓存中获取图片 **/  
  15.     public Bitmap getImage(final String url) {      
  16.         final String path = getDirectory() + "/" + convertUrlToFileName(url);  
  17.         File file = new File(path);  
  18.         if (file.exists()) {  
  19.             Bitmap bmp = BitmapFactory.decodeFile(path);  
  20.             if (bmp == null) {  
  21.                 file.delete();  
  22.             } else {  
  23.                 updateFileTime(path);  
  24.                 return bmp;  
  25.             }  
  26.         }  
  27.         return null;  
  28.     }  
  29.                                                                   
  30.     /** 将图片存入文件缓存 **/  
  31.     public void saveBitmap(Bitmap bm, String url) {  
  32.         if (bm == null) {  
  33.             return;  
  34.         }  
  35.         //判断sdcard上的空间  
  36.         if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {  
  37.             //SD空间不足  
  38.             return;  
  39.         }  
  40.         String filename = convertUrlToFileName(url);  
  41.         String dir = getDirectory();  
  42.         File dirFile = new File(dir);  
  43.         if (!dirFile.exists())  
  44.             dirFile.mkdirs();  
  45.         File file = new File(dir +"/" + filename);  
  46.         try {  
  47.             file.createNewFile();  
  48.             OutputStream outStream = new FileOutputStream(file);  
  49.             bm.compress(Bitmap.CompressFormat.JPEG, 100, outStream);  
  50.             outStream.flush();  
  51.             outStream.close();  
  52.         } catch (FileNotFoundException e) {  
  53.             Log.w("ImageFileCache", "FileNotFoundException");  
  54.         } catch (IOException e) {  
  55.             Log.w("ImageFileCache", "IOException");  
  56.         }  
  57.     }   
  58.                                                                   
  59.     /** 
  60.      * 计算存储目录下的文件大小, 
  61.      * 当文件总大小大于规定的CACHE_SIZE或者sdcard剩余空间小于FREE_SD_SPACE_NEEDED_TO_CACHE的规定 
  62.      * 那么删除40%最近没有被使用的文件 
  63.      */  
  64.     private boolean removeCache(String dirPath) {  
  65.         File dir = new File(dirPath);  
  66.         File[] files = dir.listFiles();  
  67.         if (files == null) {  
  68.             return true;  
  69.         }  
  70.         if (!android.os.Environment.getExternalStorageState().equals(  
  71.                 android.os.Environment.MEDIA_MOUNTED)) {  
  72.             return false;  
  73.         }  
  74.                                                               
  75.         int dirSize = 0;  
  76.         for (int i = 0; i < files.length; i++) {  
  77.             if (files[i].getName().contains(WHOLESALE_CONV)) {  
  78.                 dirSize += files[i].length();  
  79.             }  
  80.         }  
  81.                                                               
  82.         if (dirSize > CACHE_SIZE * MB || FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {  
  83.             int removeFactor = (int) ((0.4 * files.length) + 1);  
  84.             Arrays.sort(files, new FileLastModifSort());  
  85.             for (int i = 0; i < removeFactor; i++) {  
  86.                 if (files[i].getName().contains(WHOLESALE_CONV)) {  
  87.                     files[i].delete();  
  88.                 }  
  89.             }  
  90.         }  
  91.                                                               
  92.         if (freeSpaceOnSd() <= CACHE_SIZE) {  
  93.             return false;  
  94.         }  
  95.                                                                       
  96.         return true;  
  97.     }  
  98.                                                                   
  99.     /** 修改文件的最后修改时间 **/  
  100.     public void updateFileTime(String path) {  
  101.         File file = new File(path);  
  102.         long newModifiedTime = System.currentTimeMillis();  
  103.         file.setLastModified(newModifiedTime);  
  104.     }  
  105.                                                                   
  106.     /** 计算sdcard上的剩余空间 **/  
  107.     private int freeSpaceOnSd() {  
  108.         StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath());  
  109.         double sdFreeMB = ((double)stat.getAvailableBlocks() * (double) stat.getBlockSize()) / MB;  
  110.         return (int) sdFreeMB;  
  111.     }   
  112.                                                                   
  113.     /** 将url转成文件名 **/  
  114.     private String convertUrlToFileName(String url) {  
  115.         String[] strs = url.split("/");  
  116.         return strs[strs.length - 1] + WHOLESALE_CONV;  
  117.     }  
  118.                                                                   
  119.     /** 获得缓存目录 **/  
  120.     private String getDirectory() {  
  121.         String dir = getSDPath() + "/" + CACHDIR;  
  122.         return dir;  
  123.     }  
  124.                                                                   
  125.     /** 取SD卡路径 **/  
  126.     private String getSDPath() {  
  127.         File sdDir = null;  
  128.         boolean sdCardExist = Environment.getExternalStorageState().equals(  
  129.                 android.os.Environment.MEDIA_MOUNTED);  //判断sd卡是否存在  
  130.         if (sdCardExist) {  
  131.             sdDir = Environment.getExternalStorageDirectory();  //获取根目录  
  132.         }  
  133.         if (sdDir != null) {  
  134.             return sdDir.toString();  
  135.         } else {  
  136.             return "";  
  137.         }  
  138.     }   
  139.                                                               
  140.     /** 
  141.      * 根据文件的最后修改时间进行排序 
  142.      */  
  143.     private class FileLastModifSort implements Comparator<File> {  
  144.         public int compare(File arg0, File arg1) {  
  145.             if (arg0.lastModified() > arg1.lastModified()) {  
  146.                 return 1;  
  147.             } else if (arg0.lastModified() == arg1.lastModified()) {  
  148.                 return 0;  
  149.             } else {  
  150.                 return -1;  
  151.             }  
  152.         }  
  153.     }  
  154.                                                               
  155. }  


网络下载图片

[java] view plaincopy
  1. public class ImageGetFromHttp {  
  2.     private static final String LOG_TAG = "ImageGetFromHttp";  
  3.                                                              
  4.     public static Bitmap downloadBitmap(String url) {  
  5.         final HttpClient client = new DefaultHttpClient();  
  6.         final HttpGet getRequest = new HttpGet(url);  
  7.                                                                  
  8.         try {  
  9.             HttpResponse response = client.execute(getRequest);  
  10.             final int statusCode = response.getStatusLine().getStatusCode();  
  11.             if (statusCode != HttpStatus.SC_OK) {  
  12.                 Log.w(LOG_TAG, "Error " + statusCode + " while retrieving bitmap from " + url);  
  13.                 return null;  
  14.             }  
  15.                                                                      
  16.             final HttpEntity entity = response.getEntity();  
  17.             if (entity != null) {  
  18.                 InputStream inputStream = null;  
  19.                 try {  
  20.                     inputStream = entity.getContent();  
  21.                     FilterInputStream fit = new FlushedInputStream(inputStream);  
  22.                     return BitmapFactory.decodeStream(fit);  
  23.                 } finally {  
  24.                     if (inputStream != null) {  
  25.                         inputStream.close();  
  26.                         inputStream = null;  
  27.                     }  
  28.                     entity.consumeContent();  
  29.                 }  
  30.             }  
  31.         } catch (IOException e) {  
  32.             getRequest.abort();  
  33.             Log.w(LOG_TAG, "I/O error while retrieving bitmap from " + url, e);  
  34.         } catch (IllegalStateException e) {  
  35.             getRequest.abort();  
  36.             Log.w(LOG_TAG, "Incorrect URL: " + url);  
  37.         } catch (Exception e) {  
  38.             getRequest.abort();  
  39.             Log.w(LOG_TAG, "Error while retrieving bitmap from " + url, e);  
  40.         } finally {  
  41.             client.getConnectionManager().shutdown();  
  42.         }  
  43.         return null;  
  44.     }  
  45.                                                          
  46.     /* 
  47.      * An InputStream that skips the exact number of bytes provided, unless it reaches EOF. 
  48.      */  
  49.     static class FlushedInputStream extends FilterInputStream {  
  50.         public FlushedInputStream(InputStream inputStream) {  
  51.             super(inputStream);  
  52.         }  
  53.                                                          
  54.         @Override  
  55.         public long skip(long n) throws IOException {  
  56.             long totalBytesSkipped = 0L;  
  57.             while (totalBytesSkipped < n) {  
  58.                 long bytesSkipped = in.skip(n - totalBytesSkipped);  
  59.                 if (bytesSkipped == 0L) {  
  60.                     int b = read();  
  61.                     if (b < 0) {  
  62.                         break;  // we reached EOF  
  63.                     } else {  
  64.                         bytesSkipped = 1; // we read one byte  
  65.                     }  
  66.                 }  
  67.                 totalBytesSkipped += bytesSkipped;  
  68.             }  
  69.             return totalBytesSkipped;  
  70.         }  
  71.     }  
  72. }  


 权限

[html] view plaincopy
    1. <uses-permission android:name="android.permission.INTERNET" />  
    2.    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />