每個Android開發者必須知道的記憶體管理知識

oschina發表於2014-11-23

相信一步步走過來的Android從業者,每個人都會遇到OOM的情況。如何避免和防範OOM的出現,對於每一個程式設計師來說確實是一門必不可少的能力。今天我們就談談在Android平臺下記憶體的管理之道,開始今天的主題之前,先再次回顧兩個概念。

記憶體洩漏:物件在記憶體heap堆中中分配的空間,當不再使用或沒有引用指向的情況下,仍不能被GC正常回收的情況。多數出現在不合理的編碼情況下,比如在 Activity中註冊了一個廣播接收器,但是在頁面關閉的時候進行unRegister,就會出現記憶體溢位的現象。通常情況下,大量的記憶體洩漏會造成 OOM。

OOM:即OutOfMemoery,顧名思義就是指記憶體溢位了。記憶體溢位是指APP向系統申請超過最大閥值的記憶體請求,系統不會再分配多餘的空間,就會造成OOM error。在我們Android平臺下,多數情況是出現在圖片不當處理載入的時候。

記憶體管理之道嘛,無非就是先理解並找出記憶體洩漏的原因,再基於這些反式去合理的編碼,去防範進而避免記憶體開銷過大的情形。學習如何合理的管理記憶體,最好先 瞭解記憶體分配的機制和原理。只有深層次的理解了內部的原理,才能真正避免OOM的發生。但是本文就不介紹Jvm/Davilk記憶體分配的機制了,如有興 趣,請檢視歷史訊息,以前做過題為《JVM執行時資料區域分析》的分享。

Android APP的所能申請的最大記憶體大小是多少,有人說是16MB,有人又說是24MB。這種事情,還是親自用自己的手機測試下比較靠譜。測試方式也比較簡 單,Java中有個Runtime類,主要用作APP與執行環境互動,APP並不會為我們建立Runtime的例項,但是Java為我們提供了單例獲取的 方式Runtime.getRuntime()。通過maxMemory()方法獲取系統可為APP分配的最大記憶體,totalMemory() 獲取APP當前所分配的記憶體heap空間大小。我手上有兩部手機,一部Oppo find7,執行Color OS,實測最大記憶體分配為192MB;一部天語v9,執行小米系統,實測最大記憶體分配為100MB。這下看出點眉目了吧,由於Android是開源系統, 不同的手機廠商其實是擁有修改這部分許可權能力的,所以就造成了不同品牌和不同系統的手機,對於APP的記憶體支援也是不一樣的,和IOS的恆久100MB是 不同的。一般來說,手機記憶體的配置越高,廠商也會調大手機支援的記憶體最大閥值,尤其是現在旗艦機滿天釋出的情況下。但是開發者為了考慮開發出的APP的內 存相容性,無法保證APP執行在何種手機上,只能從編碼角度來優化記憶體了。

下面我們逐條來分析Android記憶體優化的關鍵點。

1、萬惡的static

static是個好東西,宣告賦值呼叫就是那麼的簡單方便,但是伴隨而來的還有效能問題。由於static宣告變數的生命週期其實是和APP的生命週期一 樣的,有點類似與Application。如果大量的使用的話,就會佔據記憶體空間不釋放,積少成多也會造成記憶體的不斷開銷,直至掛掉。static的合理 使用一般用來修飾基本資料型別或者輕量級物件,儘量避免修復集合或者大物件,常用作修飾全域性配置項、工具類方法、內部類。

2、無關引用

很多情況下,我們需求用到傳遞引用,但是我們無法確保引用傳遞出去後能否及時的回收。比如比較有代表性的Context洩漏,很多情況下當Activity 結束掉後,由於仍被其他的物件指向導致一直遲遲不能回收,這就造成了記憶體洩漏。這時可以考慮第三條建議。

3、善用SoftReference/WeakReference/LruCache

Java、Android中有沒有這樣一種機制呢,當記憶體吃緊或者GC掃過的情況下,就能及時把一些記憶體佔用給釋放掉,從而分配給需要分配的地方。答案是 肯定的,java為我們提供了兩個解決方案。如果對記憶體的開銷比較關注的APP,可以考慮使用WeakReference,當GC回收掃過這塊記憶體區域時 就會回收;如果不是那麼關注的話,可以使用SoftReference,它會在記憶體申請不足的情況下自動釋放,同樣也能解決OOM問題。同時 Android自3.0以後也推出了LruCache類,使用LRU演算法就釋放記憶體,一樣的能解決OOM,如果相容3.0一下的版本,請匯入v4包。關於 第二條的無關引用的問題,我們傳參可以考慮使用WeakReference包裝一下。

4、謹慎handler

在處理非同步操作的時候,handler + thread是個不錯的選擇。但是相信在使用handler的時候,大家都會遇到警告的情形,這個就是lint為開發者的提醒。handler執行於UI 執行緒,不斷處理來自MessageQueue的訊息,如果handler還有訊息需要處理但是Activity頁面已經結束的情況下,Activity的 引用其實並不會被回收,這就造成了記憶體洩漏。解決方案,一是在Activity的onDestroy方法中呼叫

handler.removeCallbacksAndMessages(null);取消所有的訊息的處理,包括待處理的訊息;二是宣告handler的內部類為static。

5、Bitmap終極殺手

Bitmap的不當處理極可能造成OOM,絕大多數情況都是因這個原因出現的。Bitamp點陣圖是Android中當之無愧的胖小子,所以在操作的時候當 然是十分的小心了。由於Dalivk並不會主動的去回收,需要開發者在Bitmap不被使用的時候recycle掉。使用的過程中,及時釋放是非常重要 的。同時如果需求允許,也可以去BItmap進行一定的縮放,通過BitmapFactory.Options的inSampleSize屬性進行控制。 如果僅僅只想獲得Bitmap的屬性,其實並不需要根據BItmap的畫素去分配記憶體,只需在解析讀取Bmp的時候使用 BitmapFactory.Options的inJustDecodeBounds屬性。最後建議大家在載入網路圖片的時候,使用軟引用或者弱引用並進 行本地快取,推薦使用android-universal-imageloader或者xUtils,牛人出品,必屬精品。前幾天在講《自定義控制元件(三)  繼承控制元件》的時候,也整理一個,大家可以去Github下載看看。

6、Cursor及時關閉

在查詢SQLite資料庫時,會返回一個Cursor,當查詢完畢後,及時關閉,這樣就可以把查詢的結果集及時給回收掉。

7、頁面背景和圖片載入

在佈局和程式碼中設定背景和圖片的時候,如果是純色,儘量使用color;如果是規則圖形,儘量使用shape畫圖;如果稍微複雜點,可以使用9patch圖;如果不能使用9patch的情況下,針對幾種主流解析度的機型進行切圖。

8、ListView和GridView的item快取

對於移動裝置,尤其硬體參差不齊的android生態,頁面的繪製其實是很耗時的,findViewById也是蠻慢的。所以不重用View,在有列表的時候就尤為顯著了,經常會出現滑動很卡的現象。具體參照歷史文章《說說ViewHolder的另一種寫法》

9、BroadCastReceiver、Service

繫結廣播和服務,一定要記得在不需要的時候給解綁。

10、I/O流

I/O流操作完畢,讀寫結束,記得關閉。

11、執行緒

執行緒不再需要繼續執行的時候要記得及時關閉,開啟執行緒數量不易過多,一般和自己機器核心數一樣最好,推薦開啟執行緒的時候,使用執行緒池。

12、String/StringBuffer

當有較多的字元創需要拼接的時候,推薦使用StringBuffer。

今天沒有程式碼,純文字,純手打,蠻辛苦。整理了這麼多優化的策略,相信大家在理解後使用,再也不會遇上OOM了。

相關文章