Bitmap優化及內存優化

Android系統中Bitmap是否有調用recycle方法的必要性

Bitmap需調用 recycle() 是歷史問題,在 Android 3.0之前,Bitmap 的圖片數據是在底層C中處理的,因此在 Android3.0 之前 recycle() 是應該調用的。雖然 finalize() 會調用 recycle() ,但對Java有經驗的同學應該知道只依靠 finalize() 去釋放資源是會出很多問題的.

在Android 3.0之後,圖片數據放在了Bitmap對象的一個成員變量 mBuffer[] 中。因此可以不調用recycle() .在 Bitmap 置 null 後圖片數據會被GC回收。

現在都 Android5.0 的年代了,建議不考慮支持3.0之前的版本。

實際上Bitmap.recycle()的說明上也有說明:
This is an advanced call, and normally need not be called, since the normal GC process will free up this memory when there are no more references to this bitmap.
(這是一個高級函數,一般來說沒必要調用。在沒有引用指向 Bitmap 時,GC 會自動釋放內存)

Managing Bitmap Memory 谷歌官方對於bitmap的解釋

In addition to the steps described in Caching Bitmaps, there are specific things you can do to facilitate garbage collection and bitmap reuse. The recommended strategy depends on which version(s) of Android you are targeting. The BitmapFun sample app included with this class shows you how to design your app to work efficiently across different versions of Android.

To set the stage for this lesson, here is how Android’s management of bitmap memory has evolved:

On Android Android 2.2 (API level 8) and lower, when garbage collection occurs, your app’s threads get stopped. This causes a lag that can degrade performance. Android 2.3 adds concurrent garbage collection, which means that the memory is reclaimed soon after a bitmap is no longer referenced.

On Android 2.3.3 (API level 10) and lower, the backing pixel data for a bitmap is stored in native memory. It is separate from the bitmap itself, which is stored in the Dalvik heap. The pixel data in native memory is not released in a predictable manner, potentially causing an application to briefly exceed its memory limits and crash. As of Android 3.0 (API level 11), the pixel data is stored on the Dalvik heap along with the associated bitmap.

The following sections describe how to optimize bitmap memory management for different Android versions.

內存優化

內存優化轉自:http://my.oschina.net/u/1753339/blog/223379

在你應用程序的UI界面加載一張圖片是一件很簡單的事情,但是當你需要在界面上加載一大堆圖片的時候,情況就變得複雜起來。在很多情況下,(比如使用ListView, GridView 或者 ViewPager 這樣的組件),屏幕上顯示的圖片可以通過滑動屏幕等事件不斷地增加,最終導致OOM。

爲了保證內存的使用始終維持在一個合理的範圍,通常會把被移除屏幕的圖片進行回收處理。此時垃圾回收器也會認爲你不再持有這些圖片的引用,從而對這些圖片進行GC操作。用這種思路來解決問題是非常好的,可是爲了能讓程序快速運行,在界面上迅速地加載圖片,你又必須要考慮到某些圖片被回收之後,用戶又將它重新滑入屏幕這種情況。這時重新去加載一遍剛剛加載過的圖片無疑是性能的瓶頸,你需要想辦法去避免這個情況的發生。

這個時候,使用內存緩存技術可以很好的解決這個問題,它可以讓組件快速地重新加載和處理圖片。下面我們就來看一看如何使用內存緩存技術來對圖片進行緩存,從而讓你的應用程序在加載很多圖片的時候可以提高響應速度和流暢性。

內存緩存技術對那些大量佔用應用程序寶貴內存的圖片提供了快速訪問的方法。其中最核心的類是LruCache (此類在android-support-v4的包中提供) 。這個類非常適合用來緩存圖片,它的主要算法原理是把最近使用的對象用強引用存儲在 LinkedHashMap 中,並且把最近最少使用的對象在緩存值達到預設定值之前從內存中移除。

在過去,我們經常會使用一種非常流行的內存緩存技術的實現,即軟引用或弱引用 (SoftReference or WeakReference)。但是現在已經不再推薦使用這種方式了,因爲從 Android 2.3 (API Level 9)開始,垃圾回收器會更傾向於回收持有軟引用或弱引用的對象,這讓軟引用和弱引用變得不再可靠。另外,Android 3.0 (API Level 11)中,圖片的數據會存儲在本地的內存當中,因而無法用一種可預見的方式將其釋放,這就有潛在的風險造成應用程序的內存溢出並崩潰。

爲了能夠選擇一個合適的緩存大小給LruCache, 有以下多個因素應該放入考慮範圍內,例如:

你的設備可以爲每個應用程序分配多大的內存?

設備屏幕上一次最多能顯示多少張圖片?有多少圖片需要進行預加載,因爲有可能很快也會顯示在屏幕上?

你的設備的屏幕大小和分辨率分別是多少?一個超高分辨率的設備(例如 Galaxy Nexus) 比起一個較低分辨率的設備(例如 Nexus S),在持有相同數量圖片的時候,需要更大的緩存空間。

圖片的尺寸和大小,還有每張圖片會佔據多少內存空間。

圖片被訪問的頻率有多高?會不會有一些圖片的訪問頻率比其它圖片要高?如果有的話,你也許應該讓一些圖片常駐在內存當中,或者使用多個LruCache 對象來區分不同組的圖片。

你能維持好數量和質量之間的平衡嗎?有些時候,存儲多個低像素的圖片,而在後臺去開線程加載高像素的圖片會更加的有效。

並沒有一個指定的緩存大小可以滿足所有的應用程序,這是由你決定的。你應該去分析程序內存的使用情況,然後制定出一個合適的解決方案。一個太小的緩存空間,有可能造成圖片頻繁地被釋放和重新加載,這並沒有好處。而一個太大的緩存空間,則有可能還是會引起 java.lang.OutOfMemory 的異常。

下面是一個使用 LruCache 來緩存圖片的例子:

private LruCache<String, Bitmap> mMemoryCache;  
@Override
protected void onCreate(Bundle savedInstanceState) { 
    // 獲取到可用內存的最大值,使用內存超出這個值會引起OutOfMemory異常。 
    // LruCache通過構造函數傳入緩存值,以KB爲單位。 
    int maxMemory = (int) (Runtime.getRuntime().maxMemory() /1024); 
    // 使用最大可用內存值的1/8作爲緩存的大小。 
    int cacheSize = maxMemory / 8; 
    mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { 
        @Override
        protected int sizeOf(String key, Bitmap bitmap) { 
            // 重寫此方法來衡量每張圖片的大小,默認返回圖片數量。 
            return bitmap.getByteCount() / 1024; 
        } 
    }; 
} 

public void addBitmapToMemoryCache(String key, Bitmap bitmap) { 
    if (getBitmapFromMemCache(key) == null) { 
        mMemoryCache.put(key, bitmap); 
    } 
} 

public Bitmap getBitmapFromMemCache(String key) { 
    return mMemoryCache.get(key); 
}

在這個例子當中,使用了系統分配給應用程序的八分之一內存來作爲緩存大小。在中高配置的手機當中,這大概會有4兆(32/8)的緩存空間。一個全屏幕的 GridView 使用4張 800x480分辨率的圖片來填充,則大概會佔用1.5兆的空間(800*480*4)。因此,這個緩存大小可以存儲2.5頁的圖片。

當向 ImageView 中加載一張圖片時,首先會在 LruCache 的緩存中進行檢查。如果找到了相應的鍵值,則會立刻更新ImageView ,否則開啓一個後臺線程來加載這張圖片。

public void loadBitmap(int resId, ImageView imageView) { 
    final String imageKey = String.valueOf(resId); 
    final Bitmap bitmap = getBitmapFromMemCache(imageKey); 
    if (bitmap != null) { 
        imageView.setImageBitmap(bitmap); 
    } else { 
        imageView.setImageResource(R.drawable.image_placeholder); 
        BitmapWorkerTask task = new BitmapWorkerTask(imageView); 
        task.execute(resId); 
    } 
}

BitmapWorkerTask 還要把新加載的圖片的鍵值對放到緩存中。

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { 
    // 在後臺加載圖片。 
    @Override
    protected Bitmap doInBackground(Integer... params) { 
        final Bitmap bitmap = decodeSampledBitmapFromResource( 
                getResources(), params[0], 100, 100); 
        addBitmapToMemoryCache(String.valueOf(params[0]), bitmap); 
        return bitmap; 
    } 
}

(至於對圖片鍵值對的鍵如何定義的問題? 圖片的路徑Uri)

Android 常見的內存泄漏有哪些

查詢數據庫而沒有關閉Cursor

在Android中,Cursor是很常用的一個對象,但在寫代碼是,經常會有人忘記調用close, 或者因爲代碼邏輯問題狀況導致close未被調用。

通常,在Activity中,我們可以調用startManagingCursor或直接使用managedQuery讓Activity自動管理Cursor對象。
但需要注意的是,當Activity介紹後,Cursor將不再可用!
若操作Cursor的代碼和UI不同步(如後臺線程),那沒需要先判斷Activity是否已經結束,或者在調用OnDestroy前,先等待後臺線程結束。
除此之外,以下也是比較常見的Cursor不會被關閉的情況:
雖然表面看起來,Cursor.close()已經被調用,但若出現異常,將會跳過close(),從而導致內存泄露。
所以,我們的代碼應該以如下的方式編寫:

Cursor c = queryCursor(); 
try { 
int a = c.getInt(1); 
...... 
} catch (Exception e) { 
} finally { 
c.close(); //在finally中調用close(), 保證其一定會被調用 
} 
try { 
Cursor c = queryCursor(); 
int a = c.getInt(1); 
...... 
c.close(); 
} catch (Exception e) { 
} 

調用registerReceiver後未調用unregisterReceiver().

在調用registerReceiver後,若未調用unregisterReceiver,其所佔的內存是相當大的。
而我們經常可以看到類似於如下的代碼:
這是個很嚴重的錯誤,因爲它會導致BroadcastReceiver不會被unregister而導致內存泄露。

registerReceiver(new BroadcastReceiver() { 
... 
}, filter); ... 

* 未關閉InputStream/OutputStream*

在使用文件或者訪問網絡資源時,使用了InputStream/OutputStream也會導致內存泄露

Bitmap使用後未調用recycle()
根據SDK的描述,調用recycle並不是必須的。但在實際使用時,Bitmap佔用的內存是很大的,所以當我們不再使用時,儘量調用recycle()以釋放資源。

Context泄露
這是一個很隱晦的內存泄露的情況。
先讓我們看一下以下代碼:
在這段代碼中,我們使用了一個static的Drawable對象。
這通常發生在我們需要經常調用一個Drawable,而其加載又比較耗時,不希望每次加載Activity都去創建這個Drawable的情況。
此時,使用static無疑是最快的代碼編寫方式,但是其也非常的糟糕。
當一個Drawable被附加到View時,這個View會被設置爲這個Drawable的callback (通過調用Drawable.setCallback()實現)。
這就意味着,這個Drawable擁有一個TextView的引用,而TextView又擁有一個Activity的引用。
這就會導致Activity在銷燬後,內存不會被釋放。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章