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。

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

發佈了144 篇原創文章 · 獲贊 18 · 訪問量 74萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章