Android 那些你所不知道的Bitmap对象详解
我們知道Android系統(tǒng)分配給每個(gè)應(yīng)用程序的內(nèi)存是有限的,Bitmap作為消耗內(nèi)存大戶,我們對(duì)Bitmap的管理稍有不當(dāng)就可能引發(fā)OutOfMemoryError,而B(niǎo)itmap對(duì)象在不同的Android版本中存在一些差異,今天就給大家介紹下這些差異,并提供一些在使用Bitmap的需要注意的地方。
在Android2.3.3(API 10)及之前的版本中,Bitmap對(duì)象與其像素?cái)?shù)據(jù)是分開(kāi)存儲(chǔ)的,Bitmap對(duì)象存儲(chǔ)在Dalvik heap中,而B(niǎo)itmap對(duì)象的像素?cái)?shù)據(jù)則存儲(chǔ)在Native Memory(本地內(nèi)存)中或者說(shuō)Derict Memory(直接內(nèi)存)中,這使得存儲(chǔ)在Native Memory中的像素?cái)?shù)據(jù)的釋放是不可預(yù)知的,我們可以調(diào)用recycle()方法來(lái)對(duì)Native Memory中的像素?cái)?shù)據(jù)進(jìn)行釋放,前提是你可以清楚的確定Bitmap已不再使用了,如果你調(diào)用了Bitmap對(duì)象recycle()之后再將Bitmap繪制出來(lái),就會(huì)出現(xiàn)"Canvas: trying to use a recycled bitmap"錯(cuò)誤,而在Android3.0(API 11)之后,Bitmap的像素?cái)?shù)據(jù)和Bitmap對(duì)象一起存儲(chǔ)在Dalvik heap中,所以我們不用手動(dòng)調(diào)用recycle()來(lái)釋放Bitmap對(duì)象,內(nèi)存的釋放都交給垃圾回收器來(lái)做,也許你會(huì)問(wèn),為什么我在顯示Bitmap對(duì)象的時(shí)候還是會(huì)出現(xiàn)OutOfMemoryError呢?
在說(shuō)這個(gè)問(wèn)題之前我順便提一下,在Android2.2(API 8)之前,使用的是Serial垃圾收集器,從名字可以看出這是一個(gè)單線程的收集器,這里的”單線程"的意思并不僅僅是使用一個(gè)CPU或者一條收集線程去收集垃圾,更重要的是在它進(jìn)行垃圾收集時(shí),必須暫停其他所有的工作線程,Android2.3之后,這種收集器就被代替了,使用的是并發(fā)的垃圾收集器,這意味著我們的垃圾收集線程和我們的工作線程互不影響。
簡(jiǎn)單的了解垃圾收集器之后,我們對(duì)上面的問(wèn)題舉一個(gè)簡(jiǎn)單的例子,假如系統(tǒng)啟動(dòng)了垃圾回收線程去收集垃圾,而此時(shí)我們一下子產(chǎn)生大量的Bitmap對(duì)象,此時(shí)是有可能會(huì)產(chǎn)生OutOfMemoryError,因?yàn)槔厥掌魇紫纫袛嗄硞€(gè)對(duì)象是否還存活(JAVA語(yǔ)言判斷對(duì)象是否存活使用的是根搜索算法 GC Root Tracing),然后利用垃圾回收算法來(lái)對(duì)垃圾進(jìn)行回收,不同的垃圾回收器具有不同的回收算法,這些都是需要時(shí)間的, 發(fā)生OutOfMemoryError的時(shí)候,我們要明確到底是因?yàn)閮?nèi)存泄露(Memory Leak)引發(fā)的還是內(nèi)存溢出(Memory overflow)引發(fā)的,如果是內(nèi)存泄露我們需要利用工具(比如MAT)查明內(nèi)存泄露的代碼并進(jìn)行改正,如果不存在泄露,換句話來(lái)說(shuō)就是內(nèi)存中的對(duì)象確實(shí)還必須活著,那我們可以看看是否可以通過(guò)某種途徑,減少對(duì)象對(duì)內(nèi)存的消耗,比如我們?cè)谑褂肂itmap的時(shí)候,應(yīng)該根據(jù)View的大小利用BitmapFactory.Options計(jì)算合適的inSimpleSize來(lái)對(duì)Bitmap進(jìn)行相對(duì)應(yīng)的裁剪,以減少Bitmap對(duì)內(nèi)存的使用,如果上面都做好了還是存在OutOfMemoryError(一般這種情況很少發(fā)生)的話,那我們只能調(diào)大Dalvik heap的大小了,在Android 3.1以及更高的版本中,我們可以在AndroidManifest.xml的application標(biāo)簽中增加一個(gè)值等于“true”的android:largeHeap屬性來(lái)通知Dalvik虛擬機(jī)應(yīng)用程序需要使用較大的Java Heap,但是我們也不鼓勵(lì)這么做。
在Android 2.3及以下管理Bitmap
從上面我們知道,在Android2.3及以下我們推薦使用recycle()方法來(lái)釋放內(nèi)存,我們?cè)谑褂肔istView或者GridView的時(shí)候,該在什么時(shí)候去調(diào)用recycle()呢?這里我們用到引用計(jì)數(shù),使用一個(gè)變量(dispalyRefCount)來(lái)記錄Bitmap顯示情況,如果Bitmap繪制在View上面displayRefCount加一, 否則就減一, 只有在displayResCount為0且Bitmap不為空且Bitmap沒(méi)有調(diào)用過(guò)recycle()的時(shí)候,我們才需求對(duì)該Bitmap對(duì)象進(jìn)行recycle(),所以我們需要用一個(gè)類(lèi)來(lái)包裝下Bitmap對(duì)象,代碼如下
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();}/*** 檢查圖片的一些狀態(tài),判斷是否需要調(diào)用recycle*/private synchronized void checkState() {if (displayResCount <= 0 && mHasBeenDisplayed&& hasValidBitmap()) {getBitmap().recycle();}}/*** 判斷Bitmap是否為空且是否調(diào)用過(guò)recycle()* @return*/private synchronized boolean hasValidBitmap() {Bitmap bitmap = getBitmap();return bitmap != null && !bitmap.isRecycled();}}
除了上面這個(gè)RecycleBitmapDrawable之外呢,我們還需要一個(gè)自定義的ImageView來(lái)控制什么時(shí)候顯示Bitmap以及什么時(shí)候隱藏Bitmap對(duì)象
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() {//當(dāng)View從窗口脫離的時(shí)候,清除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);}}}}
這個(gè)自定類(lèi)也比較簡(jiǎn)單,重寫(xiě)了setImageDrawable()方法,在這個(gè)方法中我們先獲取ImageView上面的圖片,然后通知之前顯示在ImageView的Drawable不在顯示了,Drawable會(huì)判斷是否需要調(diào)用recycle(),當(dāng)View從Window脫離的時(shí)候會(huì)回調(diào)onDetachedFromWindow(),我們?cè)谶@個(gè)方法中回收顯示在ImageView的圖片,具體的使用方法
ImageView imageView = new ImageView(context);imageView.setImageDrawable(new RecycleBitmapDrawable(context.getResource(), bitmap));
只需要用RecycleBitmapDrawable包裝Bitmap對(duì)象,然后設(shè)置到ImageView上面就可以啦,具體的內(nèi)存釋放我們不需要管,是不是很方便呢?這是在Android2.3以及以下的版本管理Bitmap的內(nèi)存。
在Android 3.0及以上管理Bitmap
由于在Android3.0及以上的版本中,Bitmap的像素?cái)?shù)據(jù)也存儲(chǔ)在Dalvik heap中,所以內(nèi)存的管理就直接交給垃圾回收器了,我們并不需要手動(dòng)的去釋放內(nèi)存,而今天講的主要是BitmapFactory.Options.inBitmap的這個(gè)字段,假如這個(gè)字段被設(shè)置了,我們?cè)诮獯aBitmap的時(shí)候,他會(huì)去重用inBitmap設(shè)置的Bitmap,減少內(nèi)存的分配和釋放,提高了應(yīng)用的性能,然而在Android 4.4之前,BitmapFactory.Options.inBitmap設(shè)置的Bitmap必須和我們需要解碼的Bitmap的大小一致才行,在Android4.4以后,BitmapFactory.Options.inBitmap設(shè)置的Bitmap大于或者等于我們需要解碼的Bitmap的大小就OK了,我們先假設(shè)一個(gè)場(chǎng)景,還是在使用ListView,GridView去加載大量的圖片,為了提高應(yīng)用的效率,我們通常會(huì)做相對(duì)應(yīng)的內(nèi)存緩存和硬盤(pán)緩存,這里我們只說(shuō)內(nèi)存緩存,而內(nèi)存緩存官方推薦使用LruCache, 注意LruCache只是起到緩存數(shù)據(jù)作用,并沒(méi)有回收內(nèi)存。一般我們的代碼會(huì)這么寫(xiě)
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) {/*** 當(dāng)保存的BitmapDrawable對(duì)象從LruCache中移除出來(lái)的時(shí)候回調(diào)的方法*/@Overrideprotected void entryRemoved(boolean evicted, String key,BitmapDrawable oldValue, BitmapDrawable newValue) {if (hasHoneycomb()) {mReusableBitmaps.add(new SoftReference<Bitmap>(oldValue.getBitmap()));}}};}/*** 從mReusableBitmaps中獲取滿足 能設(shè)置到BitmapFactory.Options.inBitmap上面的Bitmap對(duì)象* @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是否可以設(shè)置到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設(shè)置的小就行了,對(duì)inSampleSize// 沒(méi)有限制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設(shè)置的寬高相等,且inSampleSize為1return candidate.getWidth() == targetOptions.outWidth&& candidate.getHeight() == targetOptions.outHeight&& targetOptions.inSampleSize == 1;}/*** 獲取每個(gè)像素所占用的Byte數(shù)* * @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對(duì)象的弱引用保存在一個(gè)set中,然后從set中獲取滿足BitmapFactory.Options.inBitmap條件的Bitmap對(duì)象用來(lái)提高解碼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);}
通過(guò)這篇文章你是不是對(duì)Bitmap對(duì)象有了更進(jìn)一步的了解,在應(yīng)用加載大量的Bitmap對(duì)象的時(shí)候,如果你做到上面幾點(diǎn),我相信應(yīng)用發(fā)生OutOfMemoryError的概率會(huì)很小,并且性能會(huì)得到一定的提升,我經(jīng)常會(huì)看到一些同學(xué)在評(píng)價(jià)一個(gè)圖片加載框架好不好的時(shí)候,比較片面的以自己使用過(guò)程中是否發(fā)生OutOfMemoryError來(lái)定論,當(dāng)然經(jīng)常性的發(fā)生OutOfMemoryError你應(yīng)該先檢查你的代碼是否存在問(wèn)題,一般一些比較成熟的框架是不存在很?chē)?yán)重的問(wèn)題,畢竟它也經(jīng)過(guò)很多的考驗(yàn)才被人熟知的,今天的講解就到這里了,有疑問(wèn)的同學(xué)可以在下面留言!
原文:http://blog.csdn.net/xiaanming/article/details/41084843
總結(jié)
以上是生活随笔為你收集整理的Android 那些你所不知道的Bitmap对象详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: HashMap can be repla
- 下一篇: Displaying Bitmaps E