• Android图片缓存分析(一)


    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         @Override
    167         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

  • 相关阅读:
    Perl 常用的小细节总结
    Linux-root管理员创建新用户
    Linux-普通用户和root用户任意切换
    Linux-设置终端界面的字体颜色和自定义常用快捷功能
    Linux命令行好玩的命令
    如何配一副好的眼镜
    linux下面升级 Python版本并修改yum属性信息
    mysql远程登陆
    Linux 如何找到100M以上的大文件
    将raspberry 3B+的apt替换为国内源
  • 原文地址:https://www.cnblogs.com/tyrion/p/4379478.html
Copyright © 2020-2023  润新知