Android中写应用时,经常会遇到加载图片的事,由于很多图片是网络上下载获取的,当我们进页面时,便会去网络下载图片,一两次可能没啥问题,但如果同一张图片每次都去网络拉取,不仅速度慢,更影响用户体验,同时会浪费用户的流量。

基于此,很多人便想到了图片缓存的方法。

现在比较普遍的图片缓存主要有以下几个步骤:

一、从缓存中获取图片

二、如果缓存中未获取图片,则从存储卡中获取

三、如果存储卡中未获取图片,则从网络中获取

一、从缓存中获取图片

我们知道,Android中分配给每个应用的内存空间是有限的,不能无限使用,所以我们使用缓存存储的图片也是有限的,为了更有效的利用的这有限的存储空间,程序员们便想出了使用硬引用和软引用两种方式存储图片。

首先介绍下硬引用和软引用:

硬引用:表示持有当前对象的引用是强关系的,即使oom了,gc也不会回收改对象。

软引用:如果内存空间足够,垃圾回收器不会回收它,当内存不足时,,就会回收这些对象的内存。

基于以上两种引用特性,在缓存中存储图片时,一般是先将图片存储到硬引用,当硬引用空间不足时,则将最早存储到硬引用的图片存储到软引用空间,对应代码如下:

  1 package meizu.imagecachemanager;  2   3 import android.app.ActivityManager;  4 import android.content.Context;  5 import android.graphics.Bitmap;  6 import android.support.v4.util.LruCache;  7   8 import java.lang.ref.SoftReference;  9 import java.util.LinkedHashMap; 10  11 /** 12  * Created by taomaogan on 15-3-30. 13  */ 14 public class ImageMemoryCache { 15     private static final String TAG = "Cache"; 16  17     //软引用缓存容量 18     private static final int SOFT_CACHE_SIZE = 15; 19     //硬引用缓存 20     private static LruCache<String, Bitmap> mLruCache; 21     //软引用缓存 22     private static LinkedHashMap<String, SoftReference> mSoftCache; 23    24     private int mLruCacheSize = -1; 25  26     public ImageMemoryCache(Context context) { 27         this(context, -1); 28     } 29  30     //硬引用缓存可配置 31     public ImageMemoryCache(Context context, int lruCacheSize) { 32         mLruCacheSize = lruCacheSize; 33         if (mLruCacheSize <= 0) { 34             //设置默认硬引用缓存大小 35             int memoryClass = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass(); 36             mLruCacheSize = 1024 * 1024 * memoryClass / 4; 37         } 38         mLruCache = new LruCache<String, Bitmap>(mLruCacheSize) { 39  40             @Override 41             protected int sizeOf(String key, Bitmap value) { 42                 if (value != null) { 43                     //计算每张图片的像素数量 44                     return value.getRowBytes() * value.getHeight(); 45                 } 46                 return 0; 47             } 48  49             @Override 50             protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) { 51                 if (oldValue != null) { 52                     //当强引用空间不足时,将图片存入软引用 53                     android.util.Log.d(TAG, "I am from SoftReference save!------"); 54                     mSoftCache.put(key, new SoftReference(oldValue)); 55                 } 56             } 57         }; 58  59         mSoftCache = new LinkedHashMap(SOFT_CACHE_SIZE, 0.75f, true) { 60             @Override 61             protected boolean removeEldestEntry(Entry eldest) { 62                 if (size() > SOFT_CACHE_SIZE) { 63                     return true; 64                 } 65                 return false; 66             } 67         }; 68     } 69  70     public Bitmap getBitmapFromCache(String url) { 71         Bitmap bitmap; 72         //硬引用中获取图片 73         synchronized (mLruCache) { 74             bitmap = mLruCache.get(url); 75             if (bitmap != null) { 76                 android.util.Log.d(TAG, "I am from LruCache!------"); 77                 mLruCache.remove(url); 78                 mLruCache.put(url, bitmap); 79                 return bitmap; 80             } 81         } 82         //当硬引用中未获取到图片时,从软引用中获取 83         synchronized (mSoftCache) { 84             SoftReference<Bitmap> bitmapReference = mSoftCache.get(url); 85             if (bitmapReference != null) { 86                 bitmap = bitmapReference.get(); 87                 if (bitmap != null) { 88                     mLruCache.put(url, bitmap); 89                     mSoftCache.remove(url); 90                     android.util.Log.d(TAG, "I am from SoftCache!------"); 91                     return bitmap; 92                 } else { 93                     mSoftCache.remove(url); 94                 } 95             } 96         } 97  98         return null; 99     }100 101     public void addBitmapToCache(String url, Bitmap bitmap) {102         if (bitmap != null) {103             synchronized (mLruCache) {104                 android.util.Log.d(TAG, "I am from LruCache save!------");105                 mLruCache.put(url, bitmap);106             }107         }108     }109 110 111 }

上面代码getBitmapFromCache中可以看到程序首先去硬引用中寻找图片,当寻找不到时,则去软引用中寻找。

而在构造函数中mLruCache的初始化中可以看到,当硬引用空间不足时,图片会存储到软引用空间。

二、如果缓存中未获取图片,则从存储卡中获取

在缓存中查找不到图片时,我们会考虑从sd卡获取,代码如下:

  1 package meizu.imagecachemanager;  2   3 import android.graphics.Bitmap;  4 import android.graphics.BitmapFactory;  5 import android.os.Environment;  6 import android.os.StatFs;  7 import android.widget.Filter;  8   9 import java.io.File; 10 import java.io.FileNotFoundException; 11 import java.io.FileOutputStream; 12 import java.io.IOException; 13 import java.io.OutputStream; 14 import java.lang.reflect.Array; 15 import java.util.Arrays; 16 import java.util.Comparator; 17  18 /** 19  * Created by taomaogan on 15-3-30. 20  */ 21 public class ImageFileCache { 22     private static final String TAG = "Cache"; 23  24     private static final String CACHE_DIR = "imageCache"; 25     private static final String WHOLESALE_CONV = ".cach"; 26  27     private static final int MB = 1024 * 1024; 28     private static final int CACHE_SIZE = 10; 29     private static final int FREE_SD_SPACE_NEEDED_TO_CACHE = 10; 30  31     public ImageFileCache() { 32         removeCache(getDirectory()); 33     } 34  35     public Bitmap getImageFromFile(String url) { 36         String path = getDirectory() + "/" + covertUrlToFileName(url); 37         File file = new File(path); 38         if (file.exists()) { 39             Bitmap bitmap = BitmapFactory.decodeFile(path); 40             if (bitmap == null) { 41                 file.delete(); 42             } else { 43                 updateFileTime(path); 44                 android.util.Log.d(TAG, "I am from FileCache!------"); 45                 return bitmap; 46             } 47         } 48         return null; 49     } 50  51     /** 52      * 将图片存储到sd卡 53      * @param url 54      * @param bitmap 55      */ 56     public void saveBitmap(String url, Bitmap bitmap) { 57         if (bitmap == null) { 58             return; 59         } 60  61         //保证存储控空间足够 62         if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) { 63             return; 64         } 65  66         //文件名 67         String fileName = covertUrlToFileName(url); 68         //sd卡路径 69         String dir = getDirectory(); 70         File dirFile = new File(dir); 71         //判断路径是否存在 72         if (!dirFile.exists()) { 73             dirFile.mkdirs(); 74         } 75  76         File file = new File(dir + "/" + fileName); 77         try { 78             file.createNewFile(); 79             OutputStream outputStream = new FileOutputStream(file); 80             //存储png图片,图片质量为最高,意味着当存储大图时,效率低,有可能oom 81             bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream); 82             android.util.Log.d(TAG, "I am from FileCache save!------"); 83             outputStream.flush(); 84             outputStream.close(); 85         } catch (FileNotFoundException e) { 86             e.printStackTrace(); 87         } catch (IOException e) { 88             e.printStackTrace(); 89         } 90     } 91  92     private boolean removeCache(String dirPath) { 93         File dir = new File(dirPath); 94         File[] files = dir.listFiles(); 95         if (files == null) { 96             return true; 97         } 98         //sd卡是否有读取权限 99         if (!android.os.Environment.getExternalStorageState().equals(100                 Environment.MEDIA_MOUNTED)) {101             return false;102         }103 104         int dirSize = 0;105         for (int i = 0; i < files.length; i++) {106             if (files[i].getName().contains(WHOLESALE_CONV)) {107                 dirSize += files[i].length();108             }109         }110 111         if (dirSize > CACHE_SIZE * MB || FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {112             int removeFactor = (int) ((0.4 * files.length) + 1);113             Arrays.sort(files, new FileLastModifSort());114             for (int i = 0; i < removeFactor; i++) {115                 if (files[i].getName().contains(WHOLESALE_CONV)) {116                     files[i].delete();117                 }118             }119         }120         //可用空间比需要的空间少121         if (freeSpaceOnSd() <= CACHE_SIZE) {122             return false;123         }124 125         return true;126     }127 128     public void updateFileTime(String path) {129         File file = new File(path);130         long newModifiedTime = System.currentTimeMillis();131         file.setLastModified(newModifiedTime);132     }133 134     private int freeSpaceOnSd() {135         StatFs statFs = new StatFs(Environment.getExternalStorageDirectory().getPath());136         double sdFreeMB = ((double) statFs.getAvailableBlocksLong() * (double) statFs.getBlockSizeLong()) / MB;137         return (int) sdFreeMB;138     }139 140     private String covertUrlToFileName(String url) {141         String[] strs = url.split("/");142         return strs[strs.length - 1] + WHOLESALE_CONV;143     }144 145     private String getDirectory() {146         String dir = getSDPath() + "/" + CACHE_DIR;147         return dir;148     }149 150     private String getSDPath() {151         File sdDir = null;152         boolean sdCardExist = Environment.getExternalStorageState().equals(153                 Environment.MEDIA_MOUNTED);154         if (sdCardExist) {155             sdDir = Environment.getExternalStorageDirectory();156         }157         if (sdDir != null) {158             return sdDir.toString();159         } else {160             return "";161         }162     }163 164     private class FileLastModifSort implements Comparator<File> {165 166         @Override167         public int compare(File lhs, File rhs) {168             if (lhs.lastModified() > rhs.lastModified()) {169                 return 1;170             } else if (lhs.lastModified() == rhs.lastModified()) {171                 return 0;172             } else {173                 return -1;174             }175         }176     }177  }

上面的代码很简单,saveBitmap是存储图片,存储图片之前,检查一下sd卡权限,sd卡空间大小,并去从上面的注释中我们可以看到,其实本篇文章其实是不适合大图片存储的;getImageFromFile则是从存储卡中读取图片。

如果经过缓存,sd卡还是未获取到图片,最后只能通过网络获取了。

三、如果存储卡中未获取图片,则从网络中获取

网络下载图片,这里仅支持通过http获取,代码如下:

package meizu.imagecachemanager;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import org.apache.http.HttpEntity;import org.apache.http.HttpResponse;import org.apache.http.HttpStatus;import org.apache.http.client.HttpClient;import org.apache.http.client.methods.HttpGet;import org.apache.http.impl.client.DefaultHttpClient;import java.io.FilterInputStream;import java.io.IOException;import java.io.InputStream;/** * Created by taomaogan on 15-3-30. */public class ImageGetFromHttp {    private static final String TAG = "Cache";    public static Bitmap downloadBitmap(String url) {        final HttpClient httpClient = new DefaultHttpClient();        final HttpGet httpGet = new HttpGet(url);        try {            HttpResponse httpResponse = httpClient.execute(httpGet);            int statusCode = httpResponse.getStatusLine().getStatusCode();            if (statusCode != HttpStatus.SC_OK) {                return null;            }            final HttpEntity httpEntity = httpResponse.getEntity();            if (httpEntity != null) {                InputStream inputStream = null;                try {                    inputStream = httpEntity.getContent();                    FilterInputStream fileInputStream = new FlushedInputStream(inputStream);                    android.util.Log.d(TAG, "I am from Http!------");                    return BitmapFactory.decodeStream(fileInputStream);                } finally {                    if (inputStream != null) {                        inputStream.close();                    }                    httpEntity.consumeContent();                }            }        } catch (IOException e) {            e.printStackTrace();        } catch (IllegalStateException e) {            e.printStackTrace();        } catch (Exception e) {            e.printStackTrace();        }        return null;    }    private static class FlushedInputStream extends FilterInputStream {        /**         * Constructs a new {@code FilterInputStream} with the specified input         * stream as source.         * <p/>         * <p><strong>Warning:</strong> passing a null source creates an invalid         * {@code FilterInputStream}, that fails on every method that is not         * overridden. Subclasses should check for null in their constructors.         *         * @param in the input stream to filter reads on.         */        protected FlushedInputStream(InputStream in) {            super(in);        }        @Override        public long skip(long byteCount) throws IOException {            long totalBytesSkipped = 0l;            while (totalBytesSkipped < byteCount) {                long bytesSkipped = in.skip(byteCount - totalBytesSkipped);                if (bytesSkipped == 0l) {                    int by = read();                    if (by < 0) {                        break;                    } else {                        bytesSkipped = 1;                    }                }                totalBytesSkipped += bytesSkipped;            }            return totalBytesSkipped;        }    }}

以上便是3种获取图片的流程,汇总如下:

public Bitmap getBitmap(String url) {        Bitmap bitmap = mImageMemoryCache.getBitmapFromCache(url);        if (bitmap == null) {            bitmap = mImageFileCache.getImageFromFile(url);            if (bitmap == null) {                bitmap = ImageGetFromHttp.downloadBitmap(url);                if (bitmap != null) {                    mImageFileCache.saveBitmap(url, bitmap);                    mImageMemoryCache.addBitmapToCache(url, bitmap);                }            } else {                mImageMemoryCache.addBitmapToCache(url, bitmap);            }        }        return bitmap;    }

以上代码是先从缓存读取图片,然后从文件读取图片,最后从网络下载图片,需要注意的是,从网络下载图片成功后,分别将其存储到sd卡和缓存中,不然以上实现的缓存便无意义了。

以上便是图片缓存的比较简单流程了,可以在demo中使用,为什么是仅仅在demo中使用呢?因为真正的图片下载缓存还需要用到线程池,像以上图片下载每次都需要手动新建线程,还是比较麻烦。

上面图片缓存的优点是:增强了用户体验,节省了用户流量

       缺点是:加载大图片时容易产生oom问题

关于以上问题,以后会继续分析。

引用:

http://keegan-lee.diandian.com/post/2012-12-06/40047548955

源码:

https://github.com/taothreeyears/ImageCache

更多相关文章

  1. Android中图片的三级缓存策略
  2. Android小项目——社交类app(低仿微信)
  3. Android:调用系统图库/裁剪图片
  4. Android(安卓)自定义textview 部分文字字体颜色高亮
  5. 安卓图片反复压缩后为什么普遍会变绿而不是其它颜色?
  6. Android中圆形图的几种实现方式
  7. Android(安卓)强弱指针分析
  8. Android实现图片相似度
  9. 智能指针

随机推荐

  1. Android(安卓)SDK中的必会工具——androi
  2. Android(安卓)Studio 单刷《第一行代码》
  3. android ndk 开发之 在 应用程序中使用 j
  4. 搭建 Android(安卓)2.2 开发环境
  5. Android磁盘管理-系统源码分析(1)
  6. 在 Android(安卓)4.1上,分析 input -- and
  7. Android读写XML(上)
  8. Android(安卓)图形系统剖析
  9. Android(安卓)文件系统及权限修改
  10. Android属性gravity与layout_gravity的区