標籤:previous ext.get extends turn pack max grid data alpha
轉載請註明本文出自xiaanming的部落格(http://blog.csdn.net/xiaanming/article/details/41084843),請尊重他人的辛勤勞動成果,謝謝。
我們知道Android系統分配給每一個應用程式的記憶體是有限的,Bitmap作為消耗記憶體大戶。我們對Bitmap的管理稍有不當就可能引發OutOfMemoryError,而Bitmap對象在不同的Android版本號碼中存在一些差異,今天就給大家介紹下這些差異。並提供一些在使用Bitmap的須要注意的地方。
在Android2.3.3(API 10)及之前的版本號碼中,Bitmap對象與其像素資料是分開儲存的,BitmapObject Storage Service在Dalvik heap中,而Bitmap對象的像素資料則儲存在Native Memory(本地記憶體)中或者說Derict Memory(直接記憶體)中,這使得儲存在Native Memory中的像素資料的釋放是不可預知的。我們能夠調用recycle()方法來對Native Memory中的像素資料進行釋放,前提是你能夠清楚的確定Bitmap已不再使用了,假設你調用了Bitmap對象recycle()之後再將Bitmap繪製出來,就會出現"Canvas: trying to use a recycled bitmap"錯誤。而在Android3.0(API 11)之後,Bitmap的像素資料和Bitmap對象一起儲存在Dalvik heap中,所以我們不用手動調用recycle()來釋放Bitmap對象,記憶體的釋放都交給記憶體回收行程來做,或許你會問,為什麼我在顯示Bitmap對象的時候還是會出現OutOfMemoryError呢?
在說這個問題之前我順便提一下。在Android2.2(API 8)之前,使用的是Serial垃圾收集器,從名字能夠看出這是一個單線程的收集器,這裡的”單線程"的意思並不僅僅是使用一個CPU或者一條收集線程去收集垃圾,更重要的是在它進行垃圾收集時,必須暫停其它全部的背景工作執行緒,Android2.3之後,這樣的收集器就被取代了,使用的是並發的垃圾收集器。這意味著我們的垃圾收集線程和我們的背景工作執行緒互不影響。
簡單的瞭解垃圾收集器之後,我們對上面的問題舉一個簡單的範例。假如系統啟動了記憶體回收線程去收集垃圾。而此時我們一下子產生大量的Bitmap對象,此時是有可能會產生OutOfMemoryError。由於記憶體回收行程首先要推斷某個對象是否還存活(JAVA語言推斷對象是否存活使用的是根搜尋演算法 GC Root Tracing)。然後利用記憶體回收演算法來對垃圾進行回收,不同的記憶體回收行程具有不同的回收演算法。這些都是須要時間的, 發生OutOfMemoryError的時候。我們要明白究竟是由於記憶體泄露(Memory Leak)引發的還是記憶體溢出(Memory overflow)引發的。假設是記憶體泄露我們須要利用工具(比方MAT)查明記憶體泄露的代碼並進行改正,假設不存在泄露,換句話來說就是記憶體中的對象確實還必須活著,那我們能夠看看能否夠通過某種途徑,降低對象對記憶體的消耗。比方我們在使用Bitmap的時候,應該依據View的大小利用BitmapFactory.Options計算合適的inSimpleSize來對Bitmap進行相相應的裁剪,以降低Bitmap對記憶體的使用,假設上面都做好了還是存在OutOfMemoryError(一般這樣的情況非常少發生)的話。那我們僅僅能調大Dalvik heap的大小了。在Android 3.1以及更高的版本號碼中,我們能夠在AndroidManifest.xml的application標籤中添加一個值等於“true”的android:largeHeap屬性來通知Dalvik虛擬機器應用程式須要使用較大的Java Heap。可是我們也不鼓舞這麼做。
在Android 2.3及下面管理Bitmap
從上面我們知道,在Android2.3及下面我們推薦使用recycle()方法來釋放記憶體。我們在使用ListView或者GridView的時候,該在什麼時候去調用recycle()呢?這裡我們用到引用計數,使用一個變數(dispalyRefCount)來記錄Bitmap顯示情況,假設Bitmap繪製在View上面displayRefCount加一, 否則就減一, 僅僅有在displayResCount為0且Bitmap不為空白且Bitmap沒有調用過recycle()的時候,我們才需求對該Bitmap對象進行recycle(),所以我們須要用一個類來封裝下Bitmap對象,代碼例如以下
package com.example.bitmap;import android.content.res.Resources;import android.graphics.Bitmap;import android.graphics.drawable.BitmapDrawable;public class RecycleBitmapDrawable extends BitmapDrawable {private int displayResCount = 0;private boolean mHasBeenDisplayed; public RecycleBitmapDrawable(Resources res, Bitmap bitmap) { super(res, bitmap); } /** * @param isDisplay */public void setIsDisplayed(boolean isDisplay){synchronized (this) {if(isDisplay){mHasBeenDisplayed = true;displayResCount ++;}else{displayResCount --;}}checkState();}/** * 檢查圖片的一些狀態,推斷是否須要調用recycle */private synchronized void checkState() { if (displayResCount <= 0 && mHasBeenDisplayed && hasValidBitmap()) { getBitmap().recycle(); }}/** * 推斷Bitmap是否為空白且是否調用過recycle() * @return */private synchronized boolean hasValidBitmap() { Bitmap bitmap = getBitmap(); return bitmap != null && !bitmap.isRecycled();}}除了上面這個RecycleBitmapDrawable之外呢,我們還須要一個自己定義的ImageView來控制什麼時候顯示Bitmap以及什麼時候隱藏Bitmap對象
package com.example.bitmap;import android.content.Context;import android.graphics.drawable.Drawable;import android.graphics.drawable.LayerDrawable;import android.util.AttributeSet;import android.widget.ImageView;public class RecycleImageView extends ImageView {public RecycleImageView(Context context) {super(context);}public RecycleImageView(Context context, AttributeSet attrs) {super(context, attrs);}public RecycleImageView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);}@Overridepublic void setImageDrawable(Drawable drawable) {Drawable previousDrawable = getDrawable();super.setImageDrawable(drawable);//顯示新的drawablenotifyDrawable(drawable, true);//回收之前的圖片notifyDrawable(previousDrawable, false);}@Overrideprotected void onDetachedFromWindow() {//當View從表單脫離的時候,清除drawablesetImageDrawable(null);super.onDetachedFromWindow();}/** * 通知該drawable顯示或者隱藏 * * @param drawable * @param isDisplayed */public static void notifyDrawable(Drawable drawable, boolean isDisplayed) {if (drawable instanceof RecycleBitmapDrawable) {((RecycleBitmapDrawable) drawable).setIsDisplayed(isDisplayed);} else if (drawable instanceof LayerDrawable) {LayerDrawable layerDrawable = (LayerDrawable) drawable;for (int i = 0, z = layerDrawable.getNumberOfLayers(); i < z; i++) {notifyDrawable(layerDrawable.getDrawable(i), isDisplayed);}}}}這個自定類也比較簡單,重寫了setImageDrawable()方法,在這種方法中我們先擷取ImageView上面的圖片,然後通知之前顯示在ImageView的Drawable不在顯示了,Drawable會推斷是否須要調用recycle(),當View從Window脫離的時候會回調onDetachedFromWindow(),我們在這種方法中回收顯示在ImageView的圖片,詳細的用法
ImageView imageView = new ImageView(context);imageView.setImageDrawable(new RecycleBitmapDrawable(context.getResource(), bitmap));
僅僅須要用RecycleBitmapDrawable封裝Bitmap對象。然後設定到ImageView上面就能夠啦,詳細的記憶體釋放我們不須要管,是不是非常方便呢?這是在Android2.3以及下面的版本號碼管理Bitmap的記憶體。
在Android 3.0及以上管理Bitmap
由於在Android3.0及以上的版本號碼中,Bitmap的像素資料也儲存在Dalvik heap中,所以記憶體的管理就直接交給記憶體回收行程了,我們並不須要手動的去釋放記憶體,而今天講的主要是BitmapFactory.Options.inBitmap的這個欄位,假如這個欄位被設定了,我們在解碼Bitmap的時候。他會去重用inBitmap設定的Bitmap,降低記憶體的分配和釋放,提高了應用的效能。然而在Android 4.4之前,BitmapFactory.Options.inBitmap設定的Bitmap必須和我們須要解碼的Bitmap的大小一致才行。在Android4.4以後,BitmapFactory.Options.inBitmap設定的Bitmap大於或者等於我們須要解碼的Bitmap的大小就OK了,我們先假設一個情境,還是在使用ListView。GridView去載入大量的圖片。為了提高應用的效率,我們一般會做相相應的記憶體緩衝和硬碟緩衝,這裡我們僅僅說記憶體緩衝,而記憶體緩衝官方推薦使用LruCache, 注意LruCache僅僅是起到快取資料作用,並沒有回收記憶體。一般我們的代碼會這麼寫
package com.example.bitmap;import java.lang.ref.SoftReference;import java.util.Collections;import java.util.HashSet;import java.util.Iterator;import java.util.Set;import android.annotation.TargetApi;import android.graphics.Bitmap;import android.graphics.Bitmap.Config;import android.graphics.BitmapFactory;import android.graphics.drawable.BitmapDrawable;import android.os.Build;import android.os.Build.VERSION_CODES;import android.support.v4.util.LruCache;public class ImageCache {private final static int MAX_MEMORY = 4 * 102 * 1024;private LruCache<String, BitmapDrawable> mMemoryCache;private Set<SoftReference<Bitmap>> mReusableBitmaps;private void init() {if (hasHoneycomb()) {mReusableBitmaps = Collections.synchronizedSet(new HashSet<SoftReference<Bitmap>>());}mMemoryCache = new LruCache<String, BitmapDrawable>(MAX_MEMORY) {/** * 當儲存的BitmapDrawable對象從LruCache中移除出來的時候回調的方法 */@Overrideprotected void entryRemoved(boolean evicted, String key,BitmapDrawable oldValue, BitmapDrawable newValue) {if (hasHoneycomb()) {mReusableBitmaps.add(new SoftReference<Bitmap>(oldValue.getBitmap()));}}};}/** * 從mReusableBitmaps中擷取滿足 能設定到BitmapFactory.Options.inBitmap上面的Bitmap對象 * @param options * @return */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()) {if (canUseForInBitmap(item, options)) {bitmap = item;iterator.remove();break;}} else {iterator.remove();}}}}return bitmap;}/** * 推斷該Bitmap能否夠設定到BitmapFactory.Options.inBitmap上 * * @param candidate * @param targetOptions * @return */@TargetApi(VERSION_CODES.KITKAT)public static boolean canUseForInBitmap(Bitmap candidate,BitmapFactory.Options targetOptions) {// 在Anroid4.4以後,假設要使用inBitmap的話,僅僅須要解碼的Bitmap比inBitmap設定的小即可了。對inSampleSize// 沒有限制if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {int width = targetOptions.outWidth / targetOptions.inSampleSize;int height = targetOptions.outHeight / targetOptions.inSampleSize;int byteCount = width * height* getBytesPerPixel(candidate.getConfig());return byteCount <= candidate.getAllocationByteCount();}// 在Android// 4.4之前,假設想使用inBitmap的話,解碼的Bitmap必須和inBitmap設定的寬高相等。且inSampleSize為1return candidate.getWidth() == targetOptions.outWidth&& candidate.getHeight() == targetOptions.outHeight&& targetOptions.inSampleSize == 1;}/** * 擷取每一個像素所佔用的Byte數 * * @param config * @return */public 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;}@TargetApi(VERSION_CODES.HONEYCOMB)public static boolean hasHoneycomb() {return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;}}上面僅僅是一些案例性的代碼,將從LruCache中移除的BitmapDrawable對象的弱引用儲存在一個set中,然後從set中擷取滿足BitmapFactory.Options.inBitmap條件的Bitmap對象用來提高解碼Bitmap效能。使用例如以下
public static Bitmap decodeSampledBitmapFromFile(String filename, int reqWidth, int reqHeight) { final BitmapFactory.Options options = new BitmapFactory.Options(); ... BitmapFactory.decodeFile(filename, options); ... // If we‘re running on Honeycomb or newer, try to use inBitmap. if (ImageCache.hasHoneycomb()) { options.inMutable = true; if (cache != null) { Bitmap inBitmap = cache.getBitmapFromReusableSet(options); if (inBitmap != null) { options.inBitmap = inBitmap; } } } ... return BitmapFactory.decodeFile(filename, options);}
通過這篇文章你是不是對Bitmap對象有了更進一步的瞭解,在應用載入大量的Bitmap對象的時候,假設你做到上面幾點。我相信應用發生OutOfMemoryError的機率會非常小。而且效能會得到一定的提升。我常常會看到一些同學在評價一個圖片載入架構好不好的時候。比較片面的以自己使用過程中是否發生OutOfMemoryError來定論。當然常常性的發生OutOfMemoryError你應該先檢查你的代碼是否存在問題。一般一些比較成熟的架構是不存在非常嚴重的問題,畢竟它也經過非常多的考驗才被人熟知的,今天的解說就到這裡了,有疑問的同學能夠在下面留言。
Android 那些你所不知道的Bitmap對象具體解釋