2012年10月21日 星期日

[Android] Avoid Memory leak

  • 當記憶體不足,system 就必須暫停你的 application,執行 garbage collection,通常暫停的時間很短暫讓 user 感覺不到,但若是在移動 scroll 或是遊戲戰鬥中,可以感覺到效能和回應的下降。
  • "leak" meaning you keep a reference to it thus preventing the GC from collecting it.
  • 在 Android 中,一個 Process 只能使用 16M 記憶體(模擬器為 8M?),若是超過變會引發 Out-of-Memory exception(OOM)。


容易占據大量記憶體的行為:
  • view layout
  • drawing method of a view
  • the logic code of a game


Context Leak
keeping a long-lived reference to a Context.

Context 最常用來載入和存取資源,在 app 中一般會有 Activity and Application 兩種 Context
  • setContentView(new TextView(this));
  • 表示 the entire View hierarchy and all its resources reference 到這個 activity,有可能會使得此 Context leak。

可用以下兩種方式避免 Context leak:
  • 不要將 Context 使用在自己的 scope 之外。
  • 使用 Application context。
    • This context will live as long as your application is alive and does not depend on the activities life cycle. 
    • If you plan on keeping long-lived objects that need a context, remember the application object. You can obtain it easily by calling Context.getApplicationContext() orActivity.getApplication().

In summary, to avoid context-related memory leaks, remember the following:
  • Do not keep long-lived references to a context-activity (a reference to an activity should have the same life cycle as the activity itself)
  • Try using the context-application instead of a context-activity
  • Avoid non-static inner classes in an activity if you don't control their life cycle, use a static inner class and make a weak reference to the activity inside. 
    • The solution to this issue is to use a static inner class with a WeakReference to the outer class, as done in ViewRoot and its W inner class for instance
  • A garbage collector is not an insurance against memory leaks


Bitmap Leak
  • 避免 Bitmap Leak 就必須使用 recycle 機制,適時的將不需要顯示的 Bitmap 物件回收。
  • 但還有另一種狀況: 產生 Bitmap 的時候,明明還有許多記憶體,卻發生了 OOM。
    • 假設現在產生了一個 13MB 的 Int array 物件,在使用完之後立即 recycle,照理說會空出 13MB 的空間,這時候繼續產生一個 10MB 的 Int array 物件不會有任何問題,反而生成一個 4MB 的 Bitmap 卻跳出OOM。
  • 在 Android 中,一個進程的記憶體可以由 2 個部門組成:Java 使用記憶體,C使用記憶體
    • 這兩個記憶體的和必需小於 16 MB,不然就會出現 OOM。
    • 而分配給 Java 使用的記憶體,縱使這塊記憶體被釋放了,也還是只能給Java的使用,所以就算釋放 Java 佔用的佔用了一個大塊記憶體,C 能使用的記憶體 = 16M - Java 某一瞬間占用的最大記憶體。 
    • 而 Bitmap 的生成是路程經過過程 malloc 進行記憶體分配的,佔用的是 C 的記憶體,這個也就說明了,上面所說的的 4MB Bitmap無法生成的原因,因為在 13MB 被Java用過後,剩下 C 能用的只有 3M 了。

補救方式
  • 使用 BitmapFactory.decodeStream() 產生Bitmap。
    • 儘量不要使用 setImageBitmap,setImageResource 或 BitmapFactory.decodeResource 來設置一張大圖,因為這些函數在完成 decode 後,最終都是通過 java 層的 createBitmap 來完成的,需要消耗更多記憶體。
    • 因此,改用先通過 BitmapFactory.decodeStream 方法,創建出一個 bitmap,再將其設為 ImageView 的 source,decodeStream 最大的秘密在於其直接調用 JNI >> nativeDecodeAsset() 來完成 decode,
    • 無需再使用 java 層的 createBitmap,從而節省了 java 層的空間。
    • decodeStream 直接拿的圖片來讀取位元組碼了,不會根據機器的各種解析度來自動適應,使用了 decodeStream 之後,需要在 hdpi 和 mdpi,ldpi 中配置相應的圖片資源,否則在不同解析度機器上都是同樣大小(圖元點數量),顯示出來的大小就不對了。
  • 設定圖片的 Config 參數,可以跟有效減少載入的記憶體。
    • BitmapFactory.Options inSampleSize:值越大解析度越小,佔用memory也越小。
  • 在 onPause/onStop/onDestory 時, 將沒用到的 Bitmap release(用recyle())
BitmapFactory.Options opts = new BitmapFactory.Options();
// Options 只保存图片尺寸大小,不保存图片到内存
// 缩放的比例,缩放是很难按准备的比例进行缩放的,其值表明缩放的倍数
// SDK 中建议其值是2的指数值,值越大会导致图片不清晰
opts.inSampleSize = 4;
Bitmap bmp = BitmapFactory.decodeResource(getResources(), mImageIds[position],opts);                              
 ...               
//回收
bmp.recycle();


* Reference
- Tracking Memory Allocations | Android Developers
- Avoiding memory leaks | Android Developers Blog
- (转)内存溢出的解决办法 Android - 攀升·Uranus - C++博客
- Android開發——解決載入大圖片時記憶體溢出的問題 @ 資訊園 :: 痞客邦 PIXNET ::
- GiveMePasS’s Android惡補筆記: 如何改善Bitmap所帶來的Out of Memory(OOM)問題
- Re: [問題] Bitmap OutOfMemory 永遠的痛 - 看板 AndroidDev - 批踢踢實業坊
- Andorid , Object-C and Java Programming Learning: [Android]Android Out Of Memory(OOM) 的詳細研究

沒有留言:

張貼留言