Android內存管理整理

轉自:http://my.oschina.net/jack1900/blog/293145

相信一步步走過來的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。

後面內容轉自:http://blog.csdn.net/chaihuasong/article/details/8289367

與windows內存區別
在Linux中經常發現空閒內存很少,似乎所有的內存都被系統佔用了,表面感覺是內存不夠用了,其實不然。這是Linux內存管理的一個優秀特性,在這方面,區別於 Windows的內存管理。主要特點是,無論物理內存有多大,Linux都將其充份利用,將一些程序調用過的硬盤數據讀入內存,利用內存讀寫的高速特性來提高Linux系統的數據訪問性能。而Windows是隻在需要內存時,才爲應用程序分配內存,並不能充分利用大容量的內存空間。換句話說,每增加一些物理內存,Linux都將能充分利用起來,發揮了硬件投資帶來的好處,而Windows只將其做爲擺設,即使增加8GB甚至更大。

android內存的意義
其實我們在用安卓手機的時候不用太在意剩餘內存,Android上的應用是java,當然需要虛擬機,而android上的應用是帶有獨立虛擬機的,也就是每開一個應用就會打開一個獨立的虛擬機。其實和java的垃圾回收機制類似,系統有一個規則來回收內存。進行內存調度有個閥值,只有低於這個值系統纔會按一個列表來關閉用戶不需要的東西。當然這個值默認設置得很小,所以你會看到內存老在很少的數值徘徊。但事實上他並不影響速度。相反加快了下次啓動應用的速度。這本來就是 android標榜的優勢之一,如果人爲去關閉進程,沒有太大必要。特別是使用自動關進程的軟件。爲什麼內存少的時候運行大型程序會慢呢,原因是:在內存剩餘不多時打開大型程序時會觸發系統自身的調進程調度策略,這是十分消耗系統資源的操作,特別是在一個程序頻繁向系統申請內存的時候。這種情況下系統並不會關閉所有打開的進程,而是選擇性關閉,頻繁的調度自然會拖慢系統。

進程管理軟件
進程管理軟件有無必要呢?有的。就是在運行大型程序之前,你可以手動關閉一些進程釋放內存,可以顯著的提高運行速度。但一些小程序完全可交由系統自己管理。那麼如果不關程序是不是會更耗電。android的應用在被切換到後臺時,它其實已經被暫停了,並不會消耗cpu資源只保留了運行狀態。所以爲什麼有的程序切出去重進會到主界面。但是一個程序如果想要在後臺處理些東西,如音樂播放,它就會開啓一個服務。服務可在後臺持續運行,所以在後臺耗電的也只有帶服務的應用了。我們可以把帶服務的進程用進程管理軟件關閉就可以了。沒有帶服務的應用在後臺是完全不耗電的沒有必要關閉。這種設計本來就是一個非常好的設計,下次啓動程序時會更快,因爲不需要讀取界面資源,何必要關掉他們抹殺這個android的優點呢。

Android進程種類
1. 前臺進程(foreground)
目前正在屏幕上顯示的進程和一些系統進程。舉例來說,Dialer,Storage,Google Search等系統進程就是前臺進程;再舉例來說,當你運行一個程序,如瀏覽器,當瀏覽器界面在前臺顯示時,瀏覽器屬於前臺進程(foreground),但一旦你按home回到主界面,瀏覽器就變成了後臺程序(background)。我們最不希望終止的進程就是前臺進程。

  1. 可見進程(visible)

    可見進程是一些不再前臺,但用戶依然可見的進程,舉個例來說:widget、輸入法等,都屬於visible。這部分進程雖然不在前臺,但與我們的使用也密切相關,我們也不希望它們被終止(你肯定不希望時鐘、天氣,新聞等widget被終止,那它們將無法同步,你也不希望輸入法被終止,否則你每次輸入時都需要重新啓動輸入法)

  2. 桌面進程(home app)

    即launcher,保證在多任務切換之後,可以快速返回到home界面而不需重新加載launcher
    
  3. 次要服務(secondary server)

    目前正在運行的一些服務(主要服務,如撥號等,是不可能被進程管理終止的,故這裏只談次要服務),舉例來說:谷歌企業套件,Gmail內部存儲,聯繫人內部存儲等。這部分服務雖然屬於次要服務,但很一些系統功能依然息息相關,我們時常需要用到它們,所以也太希望他們被終止
    
  4. 後臺進程(hidden)

    即是後臺進程(background),就是我們通常意義上理解的啓動後被切換到後臺的進程,如瀏覽器,閱讀器等。當程序顯示在屏幕上時,他所運行的進程即爲前臺進程(foreground),一旦我們按home返回主界面(注意是按home,不是按back),程序就駐留在後臺,成爲後臺進程(background)。後臺進程的管理策略有多種:有較爲積極的方式,一旦程序到達後臺立即終止,這種方式會提高程序的運行速度,但無法加速程序的再次啓動;也有較消極的方式,儘可能多的保留後臺程序,雖然可能會影響到單個程序的運行速度,但在再次啓動已啓動的程序時,速度會有所提升。這裏就需要用戶根據自己的使用習慣找到一個平衡點
    
  5. 內容供應節點(content provider)

    沒有程序實體,進提供內容供別的程序去用的,比如日曆供應節點,郵件供應節點等。在終止進程時,這類程序應該有較高的優先權
    
  6. 空進程(empty)

    沒有任何東西在內運行的進程,有些程序,比如BTE,在程序退出後,依然會在進程中駐留一個空進程,這個進程裏沒有任何數據在運行,作用往往是提高該程序下次的啓動速度或者記錄程序的一些歷史信息。這部分進程無疑是應該最先終止的。
    
發佈了17 篇原創文章 · 獲贊 34 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章