Android之图片缓存管理
生活随笔
收集整理的這篇文章主要介紹了
Android之图片缓存管理
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
如果每次加載同一張圖片都要從網絡獲取,那代價實在太大了。所以同一張圖片只要從網絡獲取一次就夠了,然后在本地緩存起來,之后加載同一張圖片時就從緩存中加載就可以了。從內存緩存讀取圖片是最快的,但是因為內存容量有限,所以最好再加上文件緩存。文件緩存空間也不是無限大的,容量越大讀取效率越低,因此可以設置一個限定大小比如10M,或者限定保存時間比如一天。
因此,加載圖片的流程應該是:
1、先從內存緩存中獲取,取到則返回,取不到則進行下一步;
2、從文件緩存中獲取,取到則返回并更新到內存緩存,取不到則進行下一步;
整個緩存策略是使用弱引用緩存和強引用緩存配合使用,并結合LRUCache,在盡可能地利用緩存的基礎上,也大大提高了緩存命中率。我個人覺得這個類有改進的地方,比如,當LRUCache在移除元素的時候,默認是直接刪除掉。這里更好的方式是重寫LRUCache的entryRemoved方法,使得強引用緩存滿的時候,會根據LRU算法將最近最久沒有被使用的圖片自動移入弱引用緩存,如下:
接下來看內存緩存類:ImageMemoryCache public class ImageMemoryCache {private static final int SOFT_CACHE_SIZE = 15; //軟引用緩存容量private static LruCache mLruCache; //硬引用緩存private static LinkedHashMap> mSoftCache; //軟引用緩存public ImageMemoryCache(Context context) {int memClass = ((ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();int cacheSize = 1024 * 1024 * memClass / 4; //硬引用緩存容量,為系統可用內存的1/4mLruCache = new LruCache(cacheSize) {@Overrideprotected int sizeOf(String key, Bitmap value) {if (value != null)return value.getRowBytes() * value.getHeight();elsereturn 0;}@Overrideprotected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {if (oldValue != null)// 硬引用緩存容量滿的時候,會根據LRU算法把最近沒有被使用的圖片轉入此軟引用緩存mSoftCache.put(key, new SoftReference(oldValue));}};mSoftCache = new LinkedHashMap>(SOFT_CACHE_SIZE, 0.75f, true) {private static final long serialVersionUID = 6040103833179403725L;@Overrideprotected boolean removeEldestEntry(Entry> eldest) {if (size() > SOFT_CACHE_SIZE){ return true; } return false; }};}public Bitmap getBitmapFromCache(String url) {Bitmap bitmap;//先從硬引用緩存中獲取synchronized (mLruCache) {bitmap = mLruCache.get(url);if (bitmap != null) {//如果找到的話,把元素移到LinkedHashMap的最前面,從而保證在LRU算法中是最后被刪除mLruCache.remove(url);mLruCache.put(url, bitmap);return bitmap;}}//如果硬引用緩存中找不到,到軟引用緩存中找synchronized (mSoftCache) { SoftReference bitmapReference = mSoftCache.get(url);if (bitmapReference != null) {bitmap = bitmapReference.get();if (bitmap != null) {//將圖片移回硬緩存mLruCache.put(url, bitmap);mSoftCache.remove(url);return bitmap;} else {mSoftCache.remove(url);}}}return null;} public void addBitmapToCache(String url, Bitmap bitmap) {if (bitmap != null) {synchronized (mLruCache) {mLruCache.put(url, bitmap);}}}public void clearCache() {mSoftCache.clear();} } 接下來看內存緩存類:ImageMemoryCache public class ImageFileCache {private static final String CACHDIR = "ImgCach";private static final String WHOLESALE_CONV = ".cach";private static final int MB = 1024*1024;private static final int CACHE_SIZE = 10;private static final int FREE_SD_SPACE_NEEDED_TO_CACHE = 10;public ImageFileCache() {//清理文件緩存removeCache(getDirectory());}public Bitmap getImage(final String url) { final String path = getDirectory() + "/" + convertUrlToFileName(url);File file = new File(path);if (file.exists()) {Bitmap bmp = BitmapFactory.decodeFile(path);if (bmp == null) {file.delete();} else {updateFileTime(path);return bmp;}}return null;}public void saveBitmap(Bitmap bm, String url) {if (bm == null) {return;}//判斷sdcard上的空間if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {//SD空間不足return;}String filename = convertUrlToFileName(url);String dir = getDirectory();File dirFile = new File(dir);if (!dirFile.exists())dirFile.mkdirs();File file = new File(dir +"/" + filename);try {file.createNewFile();OutputStream outStream = new FileOutputStream(file);bm.compress(Bitmap.CompressFormat.JPEG, 100, outStream);outStream.flush();outStream.close();} catch (FileNotFoundException e) {Log.w("ImageFileCache", "FileNotFoundException");} catch (IOException e) {Log.w("ImageFileCache", "IOException");}} private boolean removeCache(String dirPath) {File dir = new File(dirPath);File[] files = dir.listFiles();if (files == null) {return true;}if (!android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)) {return false;}int dirSize = 0;for (int i = 0; i < files.length; i++) {if (files[i].getName().contains(WHOLESALE_CONV)) {dirSize += files[i].length();}}if (dirSize > CACHE_SIZE * MB || FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {int removeFactor = (int) ((0.4 * files.length) + 1);Arrays.sort(files, new FileLastModifSort());for (int i = 0; i < removeFactor; i++) {if (files[i].getName().contains(WHOLESALE_CONV)) {files[i].delete();}}}if (freeSpaceOnSd() <= CACHE_SIZE) {return false;}return true;}public void updateFileTime(String path) {File file = new File(path);long newModifiedTime = System.currentTimeMillis();file.setLastModified(newModifiedTime);}private int freeSpaceOnSd() {StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath());double sdFreeMB = ((double)stat.getAvailableBlocks() * (double) stat.getBlockSize()) / MB;return (int) sdFreeMB;} private String convertUrlToFileName(String url) {String[] strs = url.split("/");return strs[strs.length - 1] + WHOLESALE_CONV;}private String getDirectory() {String dir = getSDPath() + "/" + CACHDIR;return dir;}private String getSDPath() {File sdDir = null;boolean sdCardExist = Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED); //判斷sd卡是否存在if (sdCardExist) {sdDir = Environment.getExternalStorageDirectory(); //獲取根目錄}if (sdDir != null) {return sdDir.toString();} else {return "";}} private class FileLastModifSort implements Comparator {public int compare(File arg0, File arg1) {if (arg0.lastModified() > arg1.lastModified()) {return 1;} else if (arg0.lastModified() == arg1.lastModified()) {return 0;} else {return -1;}}}}從網絡獲取圖片: public class ImageGetFromHttp {private static final String LOG_TAG = "ImageGetFromHttp";public static Bitmap downloadBitmap(String url) {final HttpClient client = new DefaultHttpClient();final HttpGet getRequest = new HttpGet(url);try {HttpResponse response = client.execute(getRequest);final int statusCode = response.getStatusLine().getStatusCode();if (statusCode != HttpStatus.SC_OK) {Log.w(LOG_TAG, "Error " + statusCode + " while retrieving bitmap from " + url);return null;}final HttpEntity entity = response.getEntity();if (entity != null) {InputStream inputStream = null;try {inputStream = entity.getContent();FilterInputStream fit = new FlushedInputStream(inputStream);return BitmapFactory.decodeStream(fit);} finally {if (inputStream != null) {inputStream.close();inputStream = null;}entity.consumeContent();}}} catch (IOException e) {getRequest.abort();Log.w(LOG_TAG, "I/O error while retrieving bitmap from " + url, e);} catch (IllegalStateException e) {getRequest.abort();Log.w(LOG_TAG, "Incorrect URL: " + url);} catch (Exception e) {getRequest.abort();Log.w(LOG_TAG, "Error while retrieving bitmap from " + url, e);} finally {client.getConnectionManager().shutdown();}return null;}static class FlushedInputStream extends FilterInputStream {public FlushedInputStream(InputStream inputStream) {super(inputStream);}@Overridepublic 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;}} } 最后,獲取一張圖片的流程就如下代碼所示:public Bitmap getBitmap(String url) {// 從內存緩存中獲取圖片Bitmap result = memoryCache.getBitmapFromCache(url);if (result == null) {// 文件緩存中獲取result = fileCache.getImage(url);if (result == null) {// 從網絡獲取result = ImageGetFromHttp.downloadBitmap(url);if (result != null) {fileCache.saveBitmap(result, url);memoryCache.addBitmapToCache(url, result);}} else {// 添加到內存緩存memoryCache.addBitmapToCache(url, result);}}return result; }
因此,加載圖片的流程應該是:
1、先從內存緩存中獲取,取到則返回,取不到則進行下一步;
2、從文件緩存中獲取,取到則返回并更新到內存緩存,取不到則進行下一步;
3、從網絡下載圖片,并更新到內存緩存和文件緩存。
后兩個步驟純碎屬于業務邏輯,暫且不表,這里來看一下手Q使用的圖片緩存管理策略。
說到緩存管理,首先談一下java中的強引用和弱引用
- 強引用:最普遍的引用,若一個對象具有強引用,那么GC絕不會回收它。如A a = new A()
- 弱引用: 弱引用又分為以下三類:
- 軟引用(SoftReference): 這類引用只有當內存空間不足GC才會回收它
- 弱引用(WeakReference): 這類引用擁有更短的生命周期,GC掃描過程中一旦發現了此類引用,不管當前內存是否足夠,立即回收
- 虛引用(PhantomRefence): 這類引用并不會決定對象的生命周期,如果一個對象僅持有虛引用,則任何時刻都可能被回收
下面來看看這樣一個圖片緩存類,為了更大限度使用緩存,它使用了強引用緩存(強引用)和弱引用緩存(弱引用)雙重緩存,強引用緩存不會輕易被回收,用來保存常用數據,不常用的轉入弱引用緩存。**
ImageCache.javapublic class ImageCache {private static final String TAG = "ImageCache";//CustomLruCache是一個繼承了LruCache的繼承類,它代表強引用緩存,它的緩存大小一般由業務方提供private CustomLruCache<String, Drawable> mMemoryCache;// Default memory cache size//這里設置的是弱引用緩存以及它所占據的空間大小private static final int DEFAULT_MEM_CACHE_SIZE = 5; // 5MBprivate final HashMap<String, WeakReference<Drawable>> mRefCache = new HashMap<String, WeakReference<Drawable>>();public ImageCache(int memSize) {memSize = Math.max(memSize, DEFAULT_MEM_CACHE_SIZE);QLog.d(TAG, "Memory cache size = " + memSize + "MB");mMemoryCache = new CustomLruCache<String, Drawable>(memSize * 1024 * 1024) {//這里重寫了LruCache的sizeOf方法,來計算每個圖片資源所占用內存的字節數@Overrideprotected int sizeOf(String key, Drawable drawable) {if (drawable instanceof BitmapDrawable) {Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();if (bitmap != null) {//若是bitmap位圖則直接計算它的大小return bitmap.getRowBytes() * bitmap.getHeight();}return 0;} else if (drawable instanceof AnimationDrawable) {//若是逐幀動畫,則首先獲取它所有的幀數,再計算總共的大小AnimationDrawable anim = (AnimationDrawable) drawable;int count = anim.getNumberOfFrames();int memSize = 0;for (int i = 0; i < count; i++) {Drawable dr = anim.getFrame(i);if (dr instanceof BitmapDrawable) {Bitmap bitmap = ((BitmapDrawable) dr).getBitmap();if (bitmap != null) {memSize += bitmap.getRowBytes() * bitmap.getHeight();}}}return memSize;}return 0;}};}//從緩存中獲取圖片public Drawable getImageFromMemCache(String key) {Drawable memDrawable = null;if (mMemoryCache != null) {//首先從強引用緩存中獲取圖片,若找到的話,把元素移動到CustomLruCache的最后面,從而保證它在LRU算法中最后被刪除?//疑問,其實LinkedHashMap本身就存在LRU的算法機制,因此,get的時候,會自動移入到隊列尾部memDrawable = mMemoryCache.remove(key);if (memDrawable != null) {memDrawable = memDrawable.getConstantState().newDrawable();mMemoryCache.put(key, memDrawable);return memDrawable;}}//強引用緩存中沒有找到,開始在弱引用緩存中查找WeakReference<Drawable> ref = mRefCache.get(key);if (ref != null) {//若找到的話,這里是否添加一步,將其從弱引用緩存移入強引用緩存中比較好memDrawable = ref.get();if (memDrawable == null) {mRefCache.remove(key);}}return memDrawable;}//添加圖片到緩存,這里不理解為什么要向強引用緩存和弱引用緩存都要添加一份public void addImageToCache(String data, Drawable drawable) {// Add to memory cacheif (mMemoryCache != null && mMemoryCache.get(data) == null) {mMemoryCache.put(data, drawable);mRefCache.put(data, new WeakReference<Drawable>(drawable));}}//從緩存中刪除資源public void removeImageFromCache(String data) {if (mRefCache != null) {mRefCache.remove(data);}if (mMemoryCache != null) {mMemoryCache.remove(data);}}public Drawable getImageFromDiskCache(String pathName) {// TODO 暫不支持disk cachereturn null;}public void clearCaches() {// mDiskCache.clearCache();mMemoryCache.evictAll();mRefCache.clear();} }整個緩存策略是使用弱引用緩存和強引用緩存配合使用,并結合LRUCache,在盡可能地利用緩存的基礎上,也大大提高了緩存命中率。我個人覺得這個類有改進的地方,比如,當LRUCache在移除元素的時候,默認是直接刪除掉。這里更好的方式是重寫LRUCache的entryRemoved方法,使得強引用緩存滿的時候,會根據LRU算法將最近最久沒有被使用的圖片自動移入弱引用緩存,如下:
mMemoryCache = new CustomLruCache<String, Drawable>(memSize * 1024 * 1024) {//這里重寫了LruCache的sizeOf方法,來計算每個圖片資源所占用內存的字節數@Overrideprotected int sizeOf(String key, Drawable drawable) {.........}當強引用緩存滿時,會自動調用這個方法,這時候,將移除的元素添加到弱引用緩存中@Overrideprotected void entryRemoved(boolean evicted, String key, Drawable oldDrawable, Drawable newDrawable) {if (oldDawable != null) {mRefCache.put(data, new WeakReference<Drawable>(oldDawable));}}};
接下來看內存緩存類:ImageMemoryCache public class ImageMemoryCache {private static final int SOFT_CACHE_SIZE = 15; //軟引用緩存容量private static LruCache mLruCache; //硬引用緩存private static LinkedHashMap> mSoftCache; //軟引用緩存public ImageMemoryCache(Context context) {int memClass = ((ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();int cacheSize = 1024 * 1024 * memClass / 4; //硬引用緩存容量,為系統可用內存的1/4mLruCache = new LruCache(cacheSize) {@Overrideprotected int sizeOf(String key, Bitmap value) {if (value != null)return value.getRowBytes() * value.getHeight();elsereturn 0;}@Overrideprotected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {if (oldValue != null)// 硬引用緩存容量滿的時候,會根據LRU算法把最近沒有被使用的圖片轉入此軟引用緩存mSoftCache.put(key, new SoftReference(oldValue));}};mSoftCache = new LinkedHashMap>(SOFT_CACHE_SIZE, 0.75f, true) {private static final long serialVersionUID = 6040103833179403725L;@Overrideprotected boolean removeEldestEntry(Entry> eldest) {if (size() > SOFT_CACHE_SIZE){ return true; } return false; }};}public Bitmap getBitmapFromCache(String url) {Bitmap bitmap;//先從硬引用緩存中獲取synchronized (mLruCache) {bitmap = mLruCache.get(url);if (bitmap != null) {//如果找到的話,把元素移到LinkedHashMap的最前面,從而保證在LRU算法中是最后被刪除mLruCache.remove(url);mLruCache.put(url, bitmap);return bitmap;}}//如果硬引用緩存中找不到,到軟引用緩存中找synchronized (mSoftCache) { SoftReference bitmapReference = mSoftCache.get(url);if (bitmapReference != null) {bitmap = bitmapReference.get();if (bitmap != null) {//將圖片移回硬緩存mLruCache.put(url, bitmap);mSoftCache.remove(url);return bitmap;} else {mSoftCache.remove(url);}}}return null;} public void addBitmapToCache(String url, Bitmap bitmap) {if (bitmap != null) {synchronized (mLruCache) {mLruCache.put(url, bitmap);}}}public void clearCache() {mSoftCache.clear();} } 接下來看內存緩存類:ImageMemoryCache public class ImageFileCache {private static final String CACHDIR = "ImgCach";private static final String WHOLESALE_CONV = ".cach";private static final int MB = 1024*1024;private static final int CACHE_SIZE = 10;private static final int FREE_SD_SPACE_NEEDED_TO_CACHE = 10;public ImageFileCache() {//清理文件緩存removeCache(getDirectory());}public Bitmap getImage(final String url) { final String path = getDirectory() + "/" + convertUrlToFileName(url);File file = new File(path);if (file.exists()) {Bitmap bmp = BitmapFactory.decodeFile(path);if (bmp == null) {file.delete();} else {updateFileTime(path);return bmp;}}return null;}public void saveBitmap(Bitmap bm, String url) {if (bm == null) {return;}//判斷sdcard上的空間if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {//SD空間不足return;}String filename = convertUrlToFileName(url);String dir = getDirectory();File dirFile = new File(dir);if (!dirFile.exists())dirFile.mkdirs();File file = new File(dir +"/" + filename);try {file.createNewFile();OutputStream outStream = new FileOutputStream(file);bm.compress(Bitmap.CompressFormat.JPEG, 100, outStream);outStream.flush();outStream.close();} catch (FileNotFoundException e) {Log.w("ImageFileCache", "FileNotFoundException");} catch (IOException e) {Log.w("ImageFileCache", "IOException");}} private boolean removeCache(String dirPath) {File dir = new File(dirPath);File[] files = dir.listFiles();if (files == null) {return true;}if (!android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)) {return false;}int dirSize = 0;for (int i = 0; i < files.length; i++) {if (files[i].getName().contains(WHOLESALE_CONV)) {dirSize += files[i].length();}}if (dirSize > CACHE_SIZE * MB || FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {int removeFactor = (int) ((0.4 * files.length) + 1);Arrays.sort(files, new FileLastModifSort());for (int i = 0; i < removeFactor; i++) {if (files[i].getName().contains(WHOLESALE_CONV)) {files[i].delete();}}}if (freeSpaceOnSd() <= CACHE_SIZE) {return false;}return true;}public void updateFileTime(String path) {File file = new File(path);long newModifiedTime = System.currentTimeMillis();file.setLastModified(newModifiedTime);}private int freeSpaceOnSd() {StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath());double sdFreeMB = ((double)stat.getAvailableBlocks() * (double) stat.getBlockSize()) / MB;return (int) sdFreeMB;} private String convertUrlToFileName(String url) {String[] strs = url.split("/");return strs[strs.length - 1] + WHOLESALE_CONV;}private String getDirectory() {String dir = getSDPath() + "/" + CACHDIR;return dir;}private String getSDPath() {File sdDir = null;boolean sdCardExist = Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED); //判斷sd卡是否存在if (sdCardExist) {sdDir = Environment.getExternalStorageDirectory(); //獲取根目錄}if (sdDir != null) {return sdDir.toString();} else {return "";}} private class FileLastModifSort implements Comparator {public int compare(File arg0, File arg1) {if (arg0.lastModified() > arg1.lastModified()) {return 1;} else if (arg0.lastModified() == arg1.lastModified()) {return 0;} else {return -1;}}}}從網絡獲取圖片: public class ImageGetFromHttp {private static final String LOG_TAG = "ImageGetFromHttp";public static Bitmap downloadBitmap(String url) {final HttpClient client = new DefaultHttpClient();final HttpGet getRequest = new HttpGet(url);try {HttpResponse response = client.execute(getRequest);final int statusCode = response.getStatusLine().getStatusCode();if (statusCode != HttpStatus.SC_OK) {Log.w(LOG_TAG, "Error " + statusCode + " while retrieving bitmap from " + url);return null;}final HttpEntity entity = response.getEntity();if (entity != null) {InputStream inputStream = null;try {inputStream = entity.getContent();FilterInputStream fit = new FlushedInputStream(inputStream);return BitmapFactory.decodeStream(fit);} finally {if (inputStream != null) {inputStream.close();inputStream = null;}entity.consumeContent();}}} catch (IOException e) {getRequest.abort();Log.w(LOG_TAG, "I/O error while retrieving bitmap from " + url, e);} catch (IllegalStateException e) {getRequest.abort();Log.w(LOG_TAG, "Incorrect URL: " + url);} catch (Exception e) {getRequest.abort();Log.w(LOG_TAG, "Error while retrieving bitmap from " + url, e);} finally {client.getConnectionManager().shutdown();}return null;}static class FlushedInputStream extends FilterInputStream {public FlushedInputStream(InputStream inputStream) {super(inputStream);}@Overridepublic 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;}} } 最后,獲取一張圖片的流程就如下代碼所示:public Bitmap getBitmap(String url) {// 從內存緩存中獲取圖片Bitmap result = memoryCache.getBitmapFromCache(url);if (result == null) {// 文件緩存中獲取result = fileCache.getImage(url);if (result == null) {// 從網絡獲取result = ImageGetFromHttp.downloadBitmap(url);if (result != null) {fileCache.saveBitmap(result, url);memoryCache.addBitmapToCache(url, result);}} else {// 添加到內存緩存memoryCache.addBitmapToCache(url, result);}}return result; }
總結
以上是生活随笔為你收集整理的Android之图片缓存管理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android之AndroidManif
- 下一篇: Android之Base64