除了緩存圖片意外,還有一些其餘的方式來促進GC的效率和圖片的複用.不一樣的Android系統版本有不一樣的處理策略.BitmapFun中就包含了這個類,可以使咱們高效地構建咱們的項目.html
爲了開始如下教程,咱們須要先介紹一下Android系統對Bitmap管理的進化史.java
如下內容描述瞭如何爲不一樣的Android版本選擇最佳的圖片緩存管理方式.android
在Android 2.3.3以及更低版本中管理緩存緩存
在Android 2.3.3(API level 10)以及更低版本中,建議使用Bitmap.recycle()方法.若是你正在展現大量的圖片,應用可能會內存溢出.這時候recycle()方法可使內存被儘量快地回收.併發
注意:你只能在肯定了Bitmap確實再也不被使用了以後才能調用recycle()方法.若是你調用recycle()方法釋放了Bitmap,而稍後卻又使用這個Bitmap,這時就會出現"Canvas: trying to use a recycled bitmap"錯誤.
ide
如下代碼片斷是recycle()方法調用的示例.這裏使用了引用計數器的方式(經過變量
uimDisplayRefCount和mCacheRefCount
)來追蹤一個Bitmap是否還在被使用或者存在於緩存中.當知足以下條件時,此處就會回收Bitmap:
引用計數器變量mDisplayRefCount和mCacheRefCount的值都爲0時.
Bitmap不爲null,並且沒有被回收.
private int mCacheRefCount = 0; private int mDisplayRefCount = 0; ... // 通知當前Drawable,當前被使用的狀態改變了 // 保持一個計數器,用來決定當前Drawable什麼時候被回收 public void setIsDisplayed(boolean isDisplayed) { synchronized (this) { if (isDisplayed) { // 當被顯示時,則計數器mDisplayRefCount的值+1 mDisplayRefCount++; // 標誌當前Drawable被顯示了 mHasBeenDisplayed = true; } else { // 當一個引用被釋放時,計數器mDisplayRefCount的值-1 mDisplayRefCount--; } } // 確認recycle()方法是否已經被調用了 checkState(); } // 通知當前Drawable緩存狀態改變了 // 保持一個計數器用於決定什麼時候這個Drawable再也不被緩存 public void setIsCached(boolean isCached) { synchronized (this) { if (isCached) { mCacheRefCount++; } else { mCacheRefCount--; } } // 確認recycle()方法是否已經被調用了 checkState(); } private synchronized void checkState() { // 若是當前Drawable的內存和展現計數器都爲0,並且當前Drawable還可用 // 則釋放掉它 if (mCacheRefCount <= 0 && mDisplayRefCount <= 0 && mHasBeenDisplayed && hasValidBitmap()) { getBitmap().recycle(); } } // 判斷Drawable對應的Bitmap是否可用 private synchronized boolean hasValidBitmap() { Bitmap bitmap = getBitmap(); return bitmap != null && !bitmap.isRecycled(); }
在Android 3.0以及更高版本中管理緩存this
從Android 3.0(API level 11)開始,引入了BitmapFactory.Options.inBitmap字段,若是這個屬性被設置了,擁有這個Options對象的方法在解析圖片的時候會嘗試複用一張已存在的圖片.這意味着圖片緩存被服用了,這意味着更流暢的用戶體驗以及更好的內存分配和回收.然而,要使用inBitmap有這必定的限制.尤爲須要注意的是,在Android 4.4(API level 19)以前,只有相同大小的Btiamp纔會被複用.更詳細的用法,請參見inBitmap的文檔.spa
保存一個Bitmap以備後來使用線程
下面的代碼片斷示範瞭如何保存一個Bitmap以備後來使用.當在Android 3.0以及更高版本平臺中時,一個Bitmap被從LrcCache中移除後,它的軟引用會被保存到一個HashSet中,以備inBitmap後來使用:
Set<SoftReference<Bitmap>> mReusableBitmaps; private LruCache<String, BitmapDrawable> mMemoryCache; // 當程序運行在Honeycomb(Android 3.1)及以上版本的系統中時,建立一個 // 同步的HashSet用於存放可複用的Bitmap的引用 if (Utils.hasHoneycomb()) { mReusableBitmaps = Collections.synchronizedSet(new HashSet<SoftReference<Bitmap>>()); } mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) { // Notify the removed entry that is no longer being cached. @Override protected void entryRemoved(boolean evicted, String key, BitmapDrawable oldValue, BitmapDrawable newValue) { if (RecyclingBitmapDrawable.class.isInstance(oldValue)) { // 若是被移除的Drawable是RecyclingBitmapDrawable,則通知它 // 已經被從內存緩存中移除 ((RecyclingBitmapDrawable) oldValue).setIsCached(false); } else { // 若是被移除的Drawable是一個普通的BitmapDrawable if (Utils.hasHoneycomb()) { // 若是運行在Honeycomb(Android 3.1)及以上系統中,則爲Bitmap添加 // 到一個保存軟引用的集合中,以備後來被inBitmap使用 mReusableBitmaps.add (new SoftReference<Bitmap>(oldValue.getBitmap())); } } } .... }
複用一個已經存在的Bitmap
在解析圖片時,會檢查是否已經存在一個可用的Bitmap,以下:
public static Bitmap decodeSampledBitmapFromFile(String filename, int reqWidth, int reqHeight, ImageCache cache) { final BitmapFactory.Options options = new BitmapFactory.Options(); ... BitmapFactory.decodeFile(filename, options); ... // 若是應用運行在Honeycomb及以上系統中,則嘗試使用inBitmap複用圖片 if (Utils.hasHoneycomb()) { addInBitmapOptions(options, cache); } ... return BitmapFactory.decodeFile(filename, options); }
如下代碼片斷描述瞭如何查找一個已經存在的Bitmap並將它設置爲inBitmap的值.注意,這個方法只有在找到了符合條件的Bitmap纔會爲inBitmap賦值(咱們不能樂觀地假定圖片會被找到):
private static void addInBitmapOptions(BitmapFactory.Options options, ImageCache cache) { // inBitmap僅能接受可編輯的Bitmap,因此此處須要強制解碼器 // 返回一個可編輯的Bitmap options.inMutable = true; if (cache != null) { // 嘗試查找知足條件的Bitmap給inBitmap賦值 Bitmap inBitmap = cache.getBitmapFromReusableSet(options); if (inBitmap != null) { // 若是查找到了符合條件的Bitmap,則賦值給inBitmap options.inBitmap = inBitmap; } } } // 這個方法遍歷了保存弱引用的集合,用於查找一個合適的Bitmap protected Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) { Bitmap bitmap = null; if (mReusableBitmaps != null && !mReusableBitmaps.isEmpty()) { synchronized (mReusableBitmaps) { final Iterator<SoftReference<Bitmap>> iterator = mReusableBitmaps.iterator(); Bitmap item; while (iterator.hasNext()) { item = iterator.next().get(); if (null != item && item.isMutable()) { // 檢查當前的Bitmap是否知足inBitmap的複用條件 // Check to see it the item can be used for inBitmap. if (canUseForInBitmap(item, options)) { // 若是知足複用條件,則給Bitmap賦值,並在方法結束時返回 bitmap = item; // 在給返回值賦值後,將當前Bitmap的引用從集合中移除 iterator.remove(); break; } } else { // 若是讀取到的是空引用,則將該引用移除 iterator.remove(); } } } } return bitmap; }
最後,下面的這個方法可以檢查哪一個Bitmap知足inBitmap的複用條件:
static boolean canUseForInBitmap( Bitmap candidate, BitmapFactory.Options targetOptions) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // 從Android 4.4(KitKat)開始,若是當前須要的Bitmap尺寸(內存中佔用的字節數)比緩存中的Bitmap尺寸小 // 而且同一張圖片的Bitmap存在,咱們就能夠複用它 int width = targetOptions.outWidth / targetOptions.inSampleSize; int height = targetOptions.outHeight / targetOptions.inSampleSize; // 當前須要的Bitmap佔用字節數 int byteCount = width * height * getBytesPerPixel(candidate.getConfig()); // 若是須要的Bitmap尺寸小於原Bitmap,則返回true return byteCount <= candidate.getAllocationByteCount(); } // 在Android 4.4之前,尺寸必須徹底一致,而且inSampleSize爲1時 // Bitmap才能被複用 return candidate.getWidth() == targetOptions.outWidth && candidate.getHeight() == targetOptions.outHeight && targetOptions.inSampleSize == 1; } /** * 一個用於判斷在不一樣的參數下,每一個像素佔用字節數的方法 */ static int getBytesPerPixel(Config config) { if (config == Config.ARGB_8888) { return 4; } else if (config == Config.RGB_565) { return 2; } else if (config == Config.ARGB_4444) { return 2; } else if (config == Config.ALPHA_8) { return 1; } return 1; }