1. 程式人生 > >Android如何避免OOM總結

Android如何避免OOM總結

前面介紹了一些基礎的記憶體管理機制以及OOM的基礎知識,那麼在實踐操作當中,有哪些指導性的規則可以參考呢?歸納下來,可以從四個方面著手,首先是減小物件的記憶體佔用,其次是記憶體物件的重複利用,然後是避免物件的記憶體洩露,最後是記憶體使用策略優化。

1)使用更加輕量的資料結構

例如,我們可以考慮使用ArrayMap/SparseArray而不是HashMap等傳統資料結構,下圖演示了HashMap的簡要工作原理,相比起Android系統專門為移動作業系統編寫的ArrayMap容器,在大多數情況下,都顯示效率低下,更佔記憶體。通常的HashMap的實現方式更加消耗記憶體,因為它需要一個額外的例項物件來記錄Mapping操作。另外,SparseArray更加高效在於他們避免了對key與value的autobox自動裝箱,並且避免了裝箱後的解箱。

2)避免在Android裡面使用Enum

3)減小Bitmap物件的記憶體佔用

Bitmap是一個極容易消耗記憶體的大胖子,減小創建出來的Bitmap的記憶體佔用是很重要的,通常來說有下面2個措施:

inSampleSize:縮放比例,在把圖片載入記憶體之前,我們需要先計算出一個合適的縮放比例,避免不必要的大圖載入。

decode format:解碼格式,選擇ARGB_8888/RBG_565/ARGB_4444/ALPHA_8,存在很大差異。

4)使用更小的圖片

在設計給到資源圖片的時候,我們需要特別留意這張圖片是否存在可以壓縮的空間,是否可以使用一張更小的圖片。儘量使用更小的圖片不僅僅可以減少記憶體的使用,還可以避免出現大量的InflationException。假設有一張很大的圖片被XML檔案直接引用,很有可能在初始化檢視的時候就會因為記憶體不足而發生InflationException,這個問題的根本原因其實是發生了OOM。

在Android上面最常用的一個快取演算法是LRU(Least Recently Use)

5)複用系統自帶的資源

Android系統本身內建了很多的資源,例如字串/顏色/圖片/動畫/樣式以及簡單佈局等等,這些資源都可以在應用程式中直接引用。這樣做不僅僅可以減少應用程式的自身負重,減小APK的大小,另外還可以一定程度上減少記憶體的開銷,複用性更好。但是也有必要留意Android系統的版本差異性,對那些不同系統版本上表現存在很大差異,不符合需求的情況,還是需要應用程式自身內建進去。

6)注意在ListView/GridView等出現大量重複子元件的視圖裡面對ConvertView的複用

7)Bitmap物件的複用

在ListView與GridView等顯示大量圖片的控制元件裡面需要使用LRU的機制來快取處理好的Bitmap。

利用inBitmap的高階特性提高Android系統在Bitmap分配與釋放執行效率上的提升(3.0以及4.4以後存在一些使用限制上的差異)。使用inBitmap屬性可以告知Bitmap解碼器去嘗試使用已經存在的記憶體區域,新解碼的bitmap會嘗試去使用之前那張bitmap在heap中所佔據的pixel data記憶體區域,而不是去問記憶體重新申請一塊區域來存放bitmap。利用這種特性,即使是上千張的圖片,也只會僅僅只需要佔用螢幕所能夠顯示的圖片數量的記憶體大小。

8)避免在onDraw方法裡面執行物件的建立

類似onDraw等頻繁呼叫的方法,一定需要注意避免在這裡做建立物件的操作,因為他會迅速增加記憶體的使用,而且很容易引起頻繁的gc,甚至是記憶體抖動。

9)StringBuilder

在有些時候,程式碼中會需要使用到大量的字串拼接的操作,這種時候有必要考慮使用StringBuilder來替代頻繁的“+”。

避免物件的記憶體洩露

記憶體物件的洩漏,會導致一些不再使用的物件無法及時釋放,這樣一方面佔用了寶貴的記憶體空間,很容易導致後續需要分配記憶體的時候,空閒空間不足而出現OOM。顯然,這還使得每級Generation的記憶體區域可用空間變小,gc就會更容易被觸發,容易出現記憶體抖動,從而引起效能問題。

10)注意Activity的洩漏

通常來說,Activity的洩漏是記憶體洩漏裡面最嚴重的問題,它佔用的記憶體多,影響面廣,我們需要特別注意以下兩種情況導致的Activity洩漏:

內部類引用導致Activity的洩漏

最典型的場景是Handler導致的Activity洩漏,如果Handler中有延遲的任務或者是等待執行的任務佇列過長,都有可能因為Handler繼續執行而導致Activity發生洩漏。此時的引用關係鏈是Looper -> MessageQueue -> Message -> Handler -> Activity。為了解決這個問題,可以在UI退出之前,執行remove Handler訊息佇列中的訊息與runnable物件。或者是使用Static + WeakReference的方式來達到斷開Handler與Activity之間存在引用關係的目的。

Activity Context被傳遞到其他例項中,這可能導致自身被引用而發生洩漏。

內部類引起的洩漏不僅僅會發生在Activity上,其他任何內部類出現的地方,都需要特別留意!我們可以考慮儘量使用static型別的內部類,同時使用WeakReference的機制來避免因為互相引用而出現的洩露。

11)考慮使用Application Context而不是Activity Context

對於大部分非必須使用Activity Context的情況(Dialog的Context就必須是Activity Context),我們都可以考慮使用Application Context而不是Activity的Context,這樣可以避免不經意的Activity洩露。

12)注意臨時Bitmap物件的及時回收

雖然在大多數情況下,我們會對Bitmap增加快取機制,但是在某些時候,部分Bitmap是需要及時回收的。例如臨時建立的某個相對比較大的bitmap物件,在經過變換得到新的bitmap物件之後,應該儘快回收原始的bitmap,這樣能夠更快釋放原始bitmap所佔用的空間。

需要特別留意的是Bitmap類裡面提供的createBitmap()方法:

這個函式返回的bitmap有可能和source bitmap是同一個,在回收的時候,需要特別檢查source bitmap與return bitmap的引用是否相同,只有在不等的情況下,才能夠執行source bitmap的recycle方法。

13)注意WebView的洩漏

Android中的WebView存在很大的相容性問題,不僅僅是Android系統版本的不同對WebView產生很大的差異,另外不同的廠商出貨的ROM裡面WebView也存在著很大的差異。更嚴重的是標準的WebView存在記憶體洩露的問題,看這裡WebView causes memory leak - leaks the parent Activity。所以通常根治這個問題的辦法是為WebView開啟另外一個程序,通過AIDL與主程序進行通訊,WebView所在的程序可以根據業務的需要選擇合適的時機進行銷燬,從而達到記憶體的完整釋放。

14)資原始檔需要選擇合適的資料夾進行存放

我們知道hdpi/xhdpi/xxhdpi等等不同dpi的資料夾下的圖片在不同的裝置上會經過scale的處理。例如我們只在hdpi的目錄下放置了一張100100的圖片,那麼根據換算關係,xxhdpi的手機去引用那張圖片就會被拉伸到200200。需要注意到在這種情況下,記憶體佔用是會顯著提高的。對於不希望被拉伸的圖片,需要放到assets或者nodpi的目錄下。

15)謹慎使用static物件

因為static的生命週期過長,和應用的程序保持一致,使用不當很可能導致物件洩漏,在Android中應該謹慎使用static物件。

16)特別留意單例物件中不合理的持有

雖然單例模式簡單實用,提供了很多便利性,但是因為單例的生命週期和應用保持一致,使用不合理很容易出現持有物件的洩漏。

17)珍惜Services資源

18)優化佈局層次,減少記憶體消耗

越扁平化的檢視佈局,佔用的記憶體就越少,效率越高。我們需要儘量保證佈局足夠扁平化,當使用系統提供的View無法實現足夠扁平的時候考慮使用自定義View來達到目的。

19)謹慎使用“抽象”程式設計

很多時候,開發者會使用抽象類作為”好的程式設計實踐”,因為抽象能夠提升程式碼的靈活性與可維護性。然而,抽象會導致一個顯著的額外記憶體開銷:他們需要同等量的程式碼用於可執行,那些程式碼會被mapping到記憶體中,因此如果你的抽象沒有顯著的提升效率,應該儘量避免他們。

20)謹慎使用多程序

使用多程序可以把應用中的部分元件執行在單獨的程序當中,這樣可以擴大應用的記憶體佔用範圍,但是這個技術必須謹慎使用,絕大多數應用都不應該貿然使用多程序,一方面是因為使用多程序會使得程式碼邏輯更加複雜,另外如果使用不當,它可能反而會導致顯著增加記憶體。當你的應用需要執行一個常駐後臺的任務,而且這個任務並不輕量,可以考慮使用這個技術。

一個典型的例子是建立一個可以長時間後臺播放的Music Player。如果整個應用都執行在一個程序中,當後臺播放的時候,前臺的那些UI資源也沒有辦法得到釋放。類似這樣的應用可以切分成2個程序:一個用來操作UI,另外一個給後臺的Service。

做好記憶體優化是一項長期的工作, 需要在很多地方注意,且行且珍惜!