1. 程式人生 > >內存泄漏和內存溢出的優化

內存泄漏和內存溢出的優化

加載速度 消息 lock recycle 手機廠商 mes 是否 生態 字符

內存泄漏: 對象在內存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

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

4、 謹慎handler

在處理異步操作的時候, handler + thread是個不錯的選擇。 但是相信在使用handler的時候, 大家都會遇到警告的情形, 這個就是lint為開發者的提
醒。 handler運行於UI線程, 不斷處理來自MessageQueue的消息, 如果handler還有消息需要處理但是Activity頁面已經結束的情況下,Activity的引用其實並不會被回收, 這就造成了內存泄漏。 解決方案, 一是在ActivityonDestroy方法中調用
handler.removeCallbacksAndMessages(null);取消所有的消息的處理, 包括待處理的消息; 二是聲明handler的內部類為static。

5Bitmap終極殺手

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

6Cursor及時關閉

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

7、 頁面背景和圖片加載

在布局和代碼中設置背景和圖片的時候, 如果是純色, 盡量使用color; 如果是規則圖形, 盡量使用shape畫圖; 如果稍微復雜點, 可以使用9patch圖; 如果不能使用9patch的情況下, 針對幾種主流分辨率的機型進行切圖。

8ListViewGridViewitem緩存

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

9BroadCastReceiverService

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

10I/O

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

11、 線程

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

12String/StringBuffer

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


內存溢出

在安卓的應用程序中,內存溢出主要提要在幾個方面

1.ListView的顯示

我們在使用ListView的時候,都會給他設置Adapter,如果在Adapter中的getView方法 中,我們沒有復用convertView,就會造成在滑動ListView的時候,會為每一個item都 生成一個View對象,而不管這個item之前是否已經生成過View對象。如果來回滑動 的次數太多的話,就會造成View生成的數量太多,最終會造成內存溢出。

如果ListView中的item是包含圖片的,那麽,如果在快速滑動的過程中,我們就去為item加載圖片,此時非常容易造成內存溢出。因為,在快速滑動的過程中,垃圾回收器還來不及回收內存,而新的item又需要新的內存來顯示圖片。所以,在這種情況下,一般的做法是監聽ListView的滾動狀態,當ListView的滾動狀態為空閑的情況下,裏面 的每一個Item才去加載圖片。

2.加載圖片相關

a、加載多張圖片

對於加載多張圖片,我們一般會使用三級緩存來實現圖片的加載。內存緩存、本地緩存、網絡緩存。緩存的目的是為了下一次加載速度更快。所以在內存中保存著一定數量的圖片是有助於下一次圖片顯示的速度。但是,內存中不能保存太多的圖片對象,所以我們使用LRUCache來保存內存中的圖片,並且控制在一定的數量之內。

b、加載單張圖片

這種情況是針對於某一張圖片特別大的情況。如果一張圖片非常非常大,如果50M,那麽我只要一去加載它,那麽我的程序肯定就會掛,根本還沒使用到三級緩存應用程序就受不了了。所以,對於大圖片的顯示需要特殊處理。因為圖片雖然特別大,但是這個圖片所需要顯示的控件有可能是很小的。我們可以先把圖片的寬和高得到,再得到這張圖片所需要顯示的控件的寬高,就可以得到圖片和控件的縮放比例了。最後,根據縮放比例,設置圖片的采樣率,來減小單張圖片的內存占用。


解決方案

無論是內存泄露還是內存溢出,最終的後果基本上是一致的,那就是造成應用程序強行關閉。在應用程序的功能開發完之後,怎麽樣才能確定應用程序有沒有內存的問題呢?又怎樣來確定到底是哪一塊代碼出的問題呢?接下來我們就來說說關於內存問題的解決方案。

1、使用monkey工具

因為有些內存問題藏的比較深,要長期使用才能出現異常。所以可以使用monKey工具 來對我們的應用程序進行壓力測試。

模擬200000次用戶操作的參考命令: adb shell monkey -s 23 -p cn.itcast.XXX(所測試的 包) --ignore-crashes --ignore-timeouts -v -v -v 200000 > D:\文件名.log

回車之後,我們所需要測試的應用程序就啟動了,並且還有不斷觸摸事件被促發,程序 生成的log會保存在D目錄下。

2、捕獲OOM異常

自定義Application並讓它實現UncaughtExceptionHandler 接口,在onCreate方法中讓自己成為系統的默認異常處理機制。Thread.setDefaultUncaughtExceptionHandler(this);

這樣子設置之後,如果應用程序出現異常,就會調用Application中的uncaughtException 方法。我們就在這個方法中判斷異常是否為OOM異常。如果是OOM異常,就將內存快照導到sd卡中去。

3.用第三方開源框架分析

leakcanary 是一個開源項目,一個內存泄露自動檢測工具,是著名的 GitHub 開源組織 Square 貢獻的,它的主要優勢就在於自動化過早的發覺內存泄露、配置簡單、抓取貼心,缺點在於還存在一些bug,不過正常使用百分之九十情況是OK的,其核心原理與MAT工具類似。

轉自:https://www.cnblogs.com/loaderman/p/6438371.html

內存泄漏和內存溢出的優化