注:本文翻譯自Google官方的Android Developers Training文檔,譯者技術(shù)一般,由于喜愛安卓而產(chǎn)生了翻譯的念頭,純屬個(gè)人興趣愛好。
原文鏈接: http://developer.android.com/training/displaying-bitmaps/manage-memory.html
接著上一節(jié)課的步伐,還有很多特定的事情可以讓垃圾回收和位圖重用變得容易。根據(jù)你的目標(biāo)Android系統(tǒng)的不同版本,推薦的策略也會(huì)有所不同。這系列課程的樣例代碼: BitmapFun (鏈接: http://www.cnblogs.com/jdneo/p/3512517.html )向你展示了你的應(yīng)用如何在不同的Android版本間有效率地工作。
在開始這堂課之前,我們先來看一下Android對(duì)于位圖存儲(chǔ)管理的演變:
- 在Android 2.2(API Level 8)之前,當(dāng)垃圾回收發(fā)生時(shí),你應(yīng)用的線程會(huì)停止。由此導(dǎo)致的延遲會(huì)降低應(yīng)用的性能表現(xiàn)。 在Android 2.3開始加入了并發(fā)式的垃圾回收,這意味著當(dāng)一個(gè)位圖不再被引用時(shí),對(duì)應(yīng)的內(nèi)存會(huì)被迅速回收 。
- 在Android 2.3.3(API Level 10)之前,一副位圖的依托像素?cái)?shù)據(jù)(backing pixel data)存儲(chǔ)于本機(jī)內(nèi)存中。它和位圖本身是分離開的,位圖存儲(chǔ)于 Dalvik堆中。本機(jī)內(nèi)存中的像素?cái)?shù)據(jù)并不會(huì)以一種可預(yù)測的形式進(jìn)行釋放,因此可能會(huì)導(dǎo)致一個(gè)應(yīng)用超過它的內(nèi)存限制而崩潰。 從Android 3.0(API Level 11)開始,像素?cái)?shù)據(jù)和與其關(guān)聯(lián)的位圖都存儲(chǔ)于Dalvik堆中了 。
下面的章節(jié)將會(huì)描述如何對(duì)不同的Android版本,優(yōu)化位圖存儲(chǔ)管理。
一). 在Android 2.3.3及之前的系統(tǒng)版本上管理內(nèi)存
在Android 2.3.3(API Level 10)及以前,推薦使用 recycle() 。如果你在你的應(yīng)用中要顯示大量的位圖數(shù)據(jù),你極有可能引起 OutOfMemoryError 錯(cuò)誤。 recycle() 可以讓應(yīng)用盡快地釋放內(nèi)存。
Caution:
只有當(dāng)你確定對(duì)應(yīng)的位圖將不再被使用的情況下,你才應(yīng)該使用 recycle() 。如果你使用了 recycle() ,并在之后嘗試?yán)L制該位圖,那么你將會(huì)得到這樣的錯(cuò)誤提示:“ Canvas: trying to use a recycled bitmap. ”
下面的代碼是調(diào)用 recycle() 的樣例。它使用引用計(jì)數(shù)(變量“ mDisplayRefCount ”以及“ mCacheRefCount ”)來追蹤位圖是否是當(dāng)前正在顯示或是在緩存中。當(dāng)下列情況發(fā)生時(shí),代碼會(huì)回收位圖:
- “ mDisplayRefCount ”以及“ mCacheRefCount ”的引用計(jì)數(shù)都為0。
- 位圖不是“ null ”,同時(shí)它還未被回收。
private int mCacheRefCount = 0 ; private int mDisplayRefCount = 0 ; ... // Notify the drawable that the displayed state has changed. // Keep a count to determine when the drawable is no longer displayed. public void setIsDisplayed( boolean isDisplayed) { synchronized ( this ) { if (isDisplayed) { mDisplayRefCount ++ ; mHasBeenDisplayed = true ; } else { mDisplayRefCount -- ; } } // Check to see if recycle() can be called. checkState(); } // Notify the drawable that the cache state has changed. // Keep a count to determine when the drawable is no longer being cached. public void setIsCached( boolean isCached) { synchronized ( this ) { if (isCached) { mCacheRefCount ++ ; } else { mCacheRefCount -- ; } } // Check to see if recycle() can be called. checkState(); } private synchronized void checkState() { // If the drawable cache and display ref counts = 0, and this drawable // has been displayed, then recycle. if (mCacheRefCount <= 0 && mDisplayRefCount <= 0 && mHasBeenDisplayed && hasValidBitmap()) { getBitmap().recycle(); } } private synchronized boolean hasValidBitmap() { Bitmap bitmap = getBitmap(); return bitmap != null && ! bitmap.isRecycled(); }
二).?在Android 3.0及以后的系統(tǒng)版本上管理內(nèi)存
Android 3.0(API Level 11)引入了
BitmapFactory.Options.inBitmap
域。如果配置了該選項(xiàng),接受該
Options
對(duì)象的解碼方法會(huì)在加載內(nèi)容時(shí)嘗試去重用已經(jīng)存在的位圖。這意味著位圖內(nèi)存被重用了,從而提高了性能表現(xiàn),同時(shí)免去了內(nèi)存分配和回收的工作。然而,對(duì)于如何使用
inBitmap
會(huì)有一些限制。特別地,在Android 4.4(API Level 19)之前,只有尺寸吻合的位圖才被支持。更多詳細(xì)信息,可以閱讀:
inBitmap
。
保存一幅位圖以備將來使用
下面的代碼樣例展示了在相同的應(yīng)用中,一個(gè)已經(jīng)存在的位圖是如何為了將來可能被再次使用到而保存的。當(dāng)一個(gè)應(yīng)用在Android 3.0及以上的設(shè)備上運(yùn)行,且一個(gè)位圖從 LruCache 中移除,該位圖的軟引用會(huì)放置在一個(gè) HashSet 中,以備之后 inBitmap 使用:
Set<SoftReference<Bitmap>> mReusableBitmaps; private LruCache<String, BitmapDrawable> mMemoryCache; // If you're running on Honeycomb or newer, create a // synchronized HashSet of references to reusable bitmaps. 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)) { // The removed entry is a recycling drawable, so notify it // that it has been removed from the memory cache. ((RecyclingBitmapDrawable) oldValue).setIsCached( false ); } else { // The removed entry is a standard BitmapDrawable. if (Utils.hasHoneycomb()) { // We're running on Honeycomb or later, so add the bitmap // to a SoftReference set for possible use with inBitmap later. mReusableBitmaps.add ( new SoftReference<Bitmap> (oldValue.getBitmap())); } } } .... }
使用一個(gè)存在的位圖
在運(yùn)行的程序中,解碼方法檢查是否有可用的位圖。例如:
public static Bitmap decodeSampledBitmapFromFile(String filename, int reqWidth, int reqHeight, ImageCache cache) { final BitmapFactory.Options options = new BitmapFactory.Options(); ... BitmapFactory.decodeFile(filename, options); ... // If we're running on Honeycomb or newer, try to use inBitmap. if (Utils.hasHoneycomb()) { addInBitmapOptions(options, cache); } ... return BitmapFactory.decodeFile(filename, options); }
下面的代碼是上述代碼中 addInBitmapOptions() 方法的實(shí)現(xiàn)。它尋找一個(gè)存在的位圖,并將它作為 inBitmap 的值。注意,該方法僅僅在它找到了一個(gè)合適的匹配時(shí),才將位圖設(shè)置為 inBitmap 的值(你的代碼不可以假定這個(gè)匹配一定能找到):
private static void addInBitmapOptions(BitmapFactory.Options options, ImageCache cache) { // inBitmap only works with mutable bitmaps, so force the decoder to // return mutable bitmaps. options.inMutable = true ; if (cache != null ) { // Try to find a bitmap to use for inBitmap. Bitmap inBitmap = cache.getBitmapFromReusableSet(options); if (inBitmap != null ) { // If a suitable bitmap has been found, set it as the value of // inBitmap. options.inBitmap = inBitmap; } } } // This method iterates through the reusable bitmaps, looking for one // to use for inBitmap: 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()) { // Check to see it the item can be used for inBitmap. if (canUseForInBitmap(item, options)) { bitmap = item; // Remove from reusable set so it can't be used again. iterator.remove(); break ; } } else { // Remove from the set if the reference has been cleared. iterator.remove(); } } } } return bitmap; }
最后,此方法決定備選的位圖是否符合 inBitmap 的尺寸標(biāo)準(zhǔn):
static boolean canUseForInBitmap( Bitmap candidate, BitmapFactory.Options targetOptions) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // From Android 4.4 (KitKat) onward we can re-use if the byte size of // the new bitmap is smaller than the reusable bitmap candidate // allocation byte count. int width = targetOptions.outWidth / targetOptions.inSampleSize; int height = targetOptions.outHeight / targetOptions.inSampleSize; int byteCount = width * height * getBytesPerPixel(candidate.getConfig()); return byteCount <= candidate.getAllocationByteCount(); } // On earlier versions, the dimensions must match exactly and the inSampleSize must be 1 return candidate.getWidth() == targetOptions.outWidth && candidate.getHeight() == targetOptions.outHeight && targetOptions.inSampleSize == 1 ; } /** * A helper function to return the byte usage per pixel of a bitmap based on its configuration. */ 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 ; }
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
