標籤:
最近除了那些忙著項目開發的事情,目前正在準備我的論文。短的時間沒有寫部落格,今晚難得想總結。只要有一點時間。因此,為了湊合用,行。嘮叨羅嗦,直接進入正題。
從事Android自移動終端的發展,想必是常常要與記憶體問題打交道的,說到Android開發中遇到的記憶體問題,像Bitmap這樣的吃記憶體的大戶略微處理不當就非常easy造成OOM,當然,眼下已經有非常多知名的開源圖片載入架構,比如:ImageLoader。Picasso等等,這些架構已經能夠非常好的攻克了Bitmap造成的OOM問題,儘管這些架構能夠節省非常多開發人員的寶貴時間。可是也會遇到一種情況。非常多剛開始學習的人僅僅是會簡單的去調用這些架構的提供的介面,被問到架構內部的一些實現原理,基本上都是腦中一片空白。從我的觀點出發,我覺得假設能夠掌握一些架構原理,想必對我們進行應用調優的意義是非常重大的,今天,主要是是想談談。假設沒有了圖片載入架構,我們要怎麼去處理Bitmap的記憶體問題呢?
談到Bitmap處理的問題,我們可能要先來瞭解一些基礎的知識,關於Bitmap在Android虛擬機器中的記憶體配置,在Google的網站上給出了以下的一段話
大致的意思也就是說。在Android3.0之前,Bitmap的記憶體配置分為兩部分,一部分是分配在Dalvik的VM堆中。而像素資料的記憶體是分配在Native堆中,而到了Android3.0之後。Bitmap的記憶體則已經所有分配在VM堆上。這兩種分配方式的差別在於,Native堆的記憶體不受Dalvik虛擬機器的管理。我們想要釋放Bitmap的記憶體,必須手動調用Recycle方法。而到了Android 3.0之後的平台,我們就能夠將Bitmap的記憶體全然放心的交給虛擬機器管理了,我們僅僅須要保證Bitmap對象遵守虛擬機器的GC Root Tracing的回收規則就可以。OK。基礎知識科普到此。接下來分幾個要點來談談怎樣最佳化Bitmap記憶體問題。
1.Bitmap的引用計數方式(針對Android3.0之前平台的最佳化方案,先上Demo Code)
private int mCacheRefCount = 0;//緩衝引用計數器private int mDisplayRefCount = 0;//顯示引用計數器...// 當前Bitmap是否被顯示在UI介面上public void setIsDisplayed(boolean isDisplayed) { synchronized (this) { if (isDisplayed) { mDisplayRefCount++; mHasBeenDisplayed = true; } else { mDisplayRefCount--; } } checkState();}//標記是否被緩衝public void setIsCached(boolean isCached) { synchronized (this) { if (isCached) { mCacheRefCount++; } else { mCacheRefCount--; } } checkState();}//用於檢測Bitmap是否已經被回收private synchronized void checkState() { if (mCacheRefCount <= 0 && mDisplayRefCount <= 0 && mHasBeenDisplayed && hasValidBitmap()) { getBitmap().recycle(); }}private synchronized boolean hasValidBitmap() { Bitmap bitmap = getBitmap(); return bitmap != null && !bitmap.isRecycled();}
上面的執行個體代碼,它使用了引用計數的方法(mDisplayRefCount 與 mCacheRefCount)來追蹤一個bitmap眼下是否有被顯示或者是在緩衝中. 當以下條件滿足時回收bitmap:
mDisplayRefCount 與 mCacheRefCount 的引用計數均為 0.
bitmap不為null, 而且它還沒有被回收.
2.使用緩衝,LruCache和DiskLruCache的結合
關於LruCache和DiskLruCache,大家一定不會陌生(有疑問的朋友能夠去API官網搜一下LruCache,而DiskLrucCache能夠參考一下這篇不錯的文章:DiskLruCache使用介紹),出於對效能和app的考慮,我們肯定是想著第一次從網路中載入到圖片之後,能夠將圖片緩衝在記憶體和sd卡中。這樣,我們就不用頻繁的去網路中載入圖片,為了非常好的控制記憶體問題,則會考慮使用LruCache作為Bitmap在記憶體中的存放容器,在sd卡則使用DiskLruCache來統一管理磁碟上的圖片緩衝。
3.SoftReference和inBitmap參數的結合
在第二點中提及到,能夠採用LruCache作為存放Bitmap的容器,而在LruCache中有一個方法值得留意,那就是entryRemoved,依照文檔給出的說法,在LruCache容器滿了須要淘汰存放當中的對象騰出空間的時候會調用此方法(注意。這裡僅僅是對象被淘汰出LruCache容器,但並不意味著對象的記憶體會馬上被Dalvik虛擬機器回收掉),此時能夠在此方法中將Bitmap使用SoftReference包裹起來,並用事先準備好的一個HashSet容器來存放這些即將被回收的Bitmap。有人會問。這樣存放有什麼意義?之所以會這樣存放,還須要再提及到inBitmap參數(在Android3.0才開始有的,詳情查閱API中的BitmapFactory.Options參數資訊)。這個參數主要是提供給我們進行複用記憶體中的Bitmap,假設設定了此參數,且滿足以下條件的時候:
- Bitmap一定要是可變的,即inmutable設定一定為ture;
- Android4.4以下的平台,須要保證inBitmap和即將要得到decode的Bitmap的尺寸規格一致;
- Android4.4及其以上的平台,僅僅須要滿足inBitmap的尺寸大於要decode得到的Bitmap的尺寸規格就可以;
在滿足以上條件的時候。系統對圖片進行decoder的時候會檢查記憶體中是否有可複用的Bitmap。避免我們頻繁的去SD卡上傳入圖片而造成系統效能的下降,畢竟從直接從記憶體中複用要比在SD卡上進行IO操作的效率要提高几十倍。寫了太多文字。以下接著給出幾段Demo Code
Set<SoftReference<Bitmap>> mReusableBitmaps;private LruCache<String, BitmapDrawable> mMemoryCache;// 用來盛放被LruCache淘汰出列的Bitmapif (Utils.hasHoneycomb()) { mReusableBitmaps = Collections.synchronizedSet(new HashSet<SoftReference<Bitmap>>());}mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) { // 當LruCache淘汰對象的時候被調用,用於在記憶體中重用Bitmap,提高負載入圖片的效能 @Override protected void entryRemoved(boolean evicted, String key, BitmapDrawable oldValue, BitmapDrawable newValue) { if (RecyclingBitmapDrawable.class.isInstance(oldValue)) { ((RecyclingBitmapDrawable) oldValue).setIsCached(false); } else { if (Utils.hasHoneycomb()) { mReusableBitmaps.add (new SoftReference<Bitmap>(oldValue.getBitmap())); } } }....}private static void addInBitmapOptions(BitmapFactory.Options options, ImageCache cache) { //將inMutable設定true,inBitmap生效的條件之中的一個 options.inMutable = true; if (cache != null) { // 嘗試尋找能夠記憶體中課複用的的Bitmap Bitmap inBitmap = cache.getBitmapFromReusableSet(options); if (inBitmap != null) { options.inBitmap = inBitmap; } }}// 擷取當前能夠滿足複用條件的Bitmap,存在則返回該Bitmap,不存在則返回nullprotected 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;}//推斷是否滿足使用inBitmap的條件static boolean canUseForInBitmap( Bitmap candidate, BitmapFactory.Options targetOptions) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // Android4.4開始,被複用的Bitmap尺寸規格大於等於須要的解碼規格就可以滿足複用條件 int width = targetOptions.outWidth / targetOptions.inSampleSize; int height = targetOptions.outHeight / targetOptions.inSampleSize; int byteCount = width * height * getBytesPerPixel(candidate.getConfig()); return byteCount <= candidate.getAllocationByteCount(); } // Android4.4之前,必須滿足被複用的Bitmap和請求的Bitmap尺寸規格一致才幹被複用 return candidate.getWidth() == targetOptions.outWidth && candidate.getHeight() == targetOptions.outHeight && targetOptions.inSampleSize == 1;}
4.減少採樣率,inSampleSize的計算
相信大家對inSampleSize是一定不會陌生的,所以此處不再做過多的介紹,關於減少採樣率對inSampleSize的計算方法。我看到網上的演算法有非常多。以下的這段演算法應該是最好的演算法了,當中還考慮了那種寬高相差非常懸殊的圖片(比如:全景)的處理。
public static int calculateInSampleSize(BitmapFactory.Options options,int reqWidth, int reqHeight) { // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) { inSampleSize *= 2; } long totalPixels = width / inSampleSize * height / inSampleSize ; final long totalReqPixelsCap = reqWidth * reqHeight * 2; while (totalPixels > totalReqPixelsCap) { inSampleSize *= 2; totalPixels /= 2; } } return inSampleSize;
5.採用decodeFileDescriptor來編碼圖片(臨時不知道原理。歡迎高手指點迷津)
關於採用decodeFileDescriptor去處理圖片能夠節省記憶體這方面。我在寫代碼的時候進行過嘗試。確實想比其它的decode方法要節省記憶體,查詢了網上的解釋。不是非常清楚,自己看了一些源碼也弄不出個名堂,為什麼使用這樣的方式就能夠節省記憶體一些呢,假設有明確當中原理的高手。歡迎解答我的疑惑。
到此,關於Bitmap處理的幾個最佳化點已經分析完成,就眼下來說,可能大家在開發的過程習慣了使用架構來載入圖片,所以不大在意圖片記憶體處理的相關問題,假設你想知道一些最佳化Bitmap記憶體原理或者想自己做一個優秀的圖片載入架構。希望本文能夠為你提供一點點思路。假設讀者覺得文章有錯誤,歡迎在下方評論中批評指正。
著作權聲明:本文部落格原創文章。部落格,未經同意,不得轉載。
Android效能最佳化:談話Bitmap記憶體管理和最佳化