内存泄漏和内存溢出的优化
內(nèi)存泄漏: 對象在內(nèi)存heap堆中中分配的空間, 當不再使用或沒有引用指向的情況下, 仍不能被GC正常回收的情況。 多數(shù)出現(xiàn)在不合理的編碼情況下, 比如在Activity中注冊了一個廣播接收器, 但是在頁面關(guān)閉的時候進行unRegister, 就會出現(xiàn)內(nèi)存溢出的現(xiàn)象。 通常情況下, 大量的內(nèi)存泄漏會造成OOM。
OOM: 即OutOfMemoery, 顧名思義就是指內(nèi)存溢出了。 內(nèi)存溢出是指APP向系統(tǒng)申請超過最大閥值的內(nèi)存請求, 系統(tǒng)不會再分配多余的空間, 就會造成OOM error。 在我們Android平臺下, 多數(shù)情況是出現(xiàn)在圖片不當處理加載的時候。內(nèi)存管理之道嘛, 無非就是先理解并找出內(nèi)存泄漏的原因, 再基于這些反式去合理的編碼, 去防范進而避免內(nèi)存開銷過大的情形。 學(xué)習(xí)如何合理的管理內(nèi)存, 最好先了解內(nèi)存分配的機制和原理。 只有深層次的理解了內(nèi)部的原理, 才能真正避免OOM的發(fā)生。
Android APP的所能申請的最大內(nèi)存大小是多少, 有人說是16MB, 有人又說是24MB。 這種事情, 還是親自用自己的手機測試下比較靠譜。 測試方式也比較簡單, Java中有個Runtime類, 主要用作APP與運行環(huán)境交互, APP并不會為我們創(chuàng)建Runtime的實例, 但是Java為我們提供了單例獲取的方式Runtime.getRuntime()。 通過maxMemory()方法獲取系統(tǒng)可為APP分配的最大內(nèi)存, totalMemory()獲取APP當前所分配的內(nèi)存heap空間大小。 我手上有兩部手機, 一部Oppo find7, 運行Color OS, 實測最大內(nèi)存分配為192MB; 一部天語v9, 運行小米系統(tǒng), 實測最大內(nèi)存分配為100MB。 這下看出點眉目了吧, 由于Android是開源系統(tǒng), 不同的手機廠商其實是擁有修改這部分權(quán)限能力的, 所以就造成了不同品牌和不同系統(tǒng)的手機, 對于APP的內(nèi)存支持也是不一樣的, 和IOS的恒久100MB是不同的。 一般來說, 手機內(nèi)存的配置越高, 廠商也會調(diào)大手機支持的內(nèi)存最大閥值, 尤其是現(xiàn)在旗艦機滿天發(fā)布的情況下。 但是開發(fā)者為了考慮開發(fā)出的APP的內(nèi)存兼容性, 無法保證APP運行在何種手機上, 只能從編碼角度來優(yōu)化內(nèi)存了。
Android內(nèi)存優(yōu)化的關(guān)鍵點。
1、 萬惡的static
static是個好東西, 聲明賦值調(diào)用就是那么的簡單方便, 但是伴隨而來的還有性能問題。 由于static聲明變量的生命周期其實是和APP的生命周期一樣的, 有點類似與Application。 如果大量的使用的話, 就會占據(jù)內(nèi)存空間不釋放, 積少成多也會造成內(nèi)存的不斷開銷, 直至掛掉。 static的合理使用一般用來修飾基本數(shù)據(jù)類型或者輕量級對象, 盡量避免修復(fù)集合或者大對象, 常用作修飾全局配置項、 工具類方法、 內(nèi)部類。
2、 無關(guān)引用
很多情況下, 我們需求用到傳遞引用, 但是我們無法確保引用傳遞出去后能否及時的回收。 比如比較有代表性的Context泄漏, 很多情況下當Activity結(jié)束掉后, 由于仍被其他的對象指向?qū)е乱恢边t遲不能回收, 這就造成了內(nèi)存泄漏。 這時可以考慮第三條建議。
3、 善用SoftReference/WeakReference/LruCache
Java、 Android中有沒有這樣一種機制呢, 當內(nèi)存吃緊或者GC掃過的情況下, 就能及時把一些內(nèi)存占用給釋放掉, 從而分配給需要分配的地方。 答案是肯定的, java為我們提供了兩個解決方案。 如果對內(nèi)存的開銷比較關(guān)注的APP, 可以考慮使用WeakReference, 當GC回收掃過這塊內(nèi)存區(qū)域時就會回收; 如果不是那么關(guān)注的話, 可以使用SoftReference, 它會在內(nèi)存申請不足的情況下自動釋放, 同樣也能解決OOM問題。 同時Android自3.0以后也推出了LruCache類, 使用LRU算法就釋放內(nèi)存, 一樣的能解決OOM, 如果兼容3.0一下的版本, 請導(dǎo)入v4包。 關(guān)于第二條的無關(guān)引用的問題, 我們傳參可以考慮使用WeakReference包裝一下。
4、 謹慎handler
在處理異步操作的時候, handler + thread是個不錯的選擇。 但是相信在使用handler的時候, 大家都會遇到警告的情形, 這個就是lint為開發(fā)者的提
醒。 handler運行于UI線程, 不斷處理來自MessageQueue的消息, 如果handler還有消息需要處理但是Activity頁面已經(jīng)結(jié)束的情況下, Activity的引用其實并不會被回收, 這就造成了內(nèi)存泄漏。 解決方案, 一是在Activity的onDestroy方法中調(diào)用
handler.removeCallbacksAndMessages(null);取消所有的消息的處理, 包括待處理的消息; 二是聲明handler的內(nèi)部類為static。
5、 Bitmap終極殺手
Bitmap的不當處理極可能造成OOM, 絕大多數(shù)情況都是因這個原因出現(xiàn)的。 Bitamp位圖是Android中當之無愧的胖小子, 所以在操作的時候當然是十分的小心了。 由于Dalivk并不會主動的去回收, 需要開發(fā)者在Bitmap不被使用的時候recycle掉。 使用的過程中, 及時釋放是非常重要的。 同時如果需求允許, 也可以去BItmap進行一定的縮放, 通過BitmapFactory.Options的inSampleSize屬性進行控制。 如果僅僅只想獲得Bitmap的屬性, 其實并不需要根據(jù)BItmap的像素去分配內(nèi)存, 只需在解析讀取Bmp的時候使用BitmapFactory.Options的inJustDecodeBounds屬性。 最后建議大家在加載網(wǎng)絡(luò)圖片的時候, 使用軟引用或者弱引用并進行本地緩存, 推薦使用android-universal-imageloader或者xUtils, 牛人出品, 必屬精品。 前幾天在講《 自定義控件( 三) 繼承控件》 的時候, 也整理一個, 大家可以去Github下載看看。
6、 Cursor及時關(guān)閉
在查詢SQLite數(shù)據(jù)庫時, 會返回一個Cursor, 當查詢完畢后, 及時關(guān)閉, 這樣就可以把查詢的結(jié)果集及時給回收掉。
7、 頁面背景和圖片加載
在布局和代碼中設(shè)置背景和圖片的時候, 如果是純色, 盡量使用color; 如果是規(guī)則圖形, 盡量使用shape畫圖; 如果稍微復(fù)雜點, 可以使用9patch圖; 如果不能使用9patch的情況下, 針對幾種主流分辨率的機型進行切圖。
8、 ListView和GridView的item緩存
對于移動設(shè)備, 尤其硬件參差不齊的android生態(tài), 頁面的繪制其實是很耗時的, findViewById也是蠻慢的。 所以不重用View, 在有列表的時候就尤為顯著了, 經(jīng)常會出現(xiàn)滑動很卡的現(xiàn)象。 具體參照歷史文章《 說說ViewHolder的另一種寫法》
9、 BroadCastReceiver、 Service
綁定廣播和服務(wù), 一定要記得在不需要的時候給解綁。
10、 I/O流
I/O流操作完畢, 讀寫結(jié)束, 記得關(guān)閉。
11、 線程
線程不再需要繼續(xù)執(zhí)行的時候要記得及時關(guān)閉, 開啟線程數(shù)量不易過多, 一般和自己機器內(nèi)核數(shù)一樣最好, 推薦開啟線程的時候, 使用線程池。
12、 String/StringBuffer
當有較多的字符創(chuàng)需要拼接的時候, 推薦使用StringBuffer。
?內(nèi)存溢出
在安卓的應(yīng)用程序中,內(nèi)存溢出主要提要在幾個方面
1.ListView的顯示
我們在使用ListView的時候,都會給他設(shè)置Adapter,如果在Adapter中的getView方法 中,我們沒有復(fù)用convertView,就會造成在滑動ListView的時候,會為每一個item都 生成一個View對象,而不管這個item之前是否已經(jīng)生成過View對象。如果來回滑動 的次數(shù)太多的話,就會造成View生成的數(shù)量太多,最終會造成內(nèi)存溢出。
如果ListView中的item是包含圖片的,那么,如果在快速滑動的過程中,我們就去為item加載圖片,此時非常容易造成內(nèi)存溢出。因為,在快速滑動的過程中,垃圾回收器還來不及回收內(nèi)存,而新的item又需要新的內(nèi)存來顯示圖片。所以,在這種情況下,一般的做法是監(jiān)聽ListView的滾動狀態(tài),當ListView的滾動狀態(tài)為空閑的情況下,里面 的每一個Item才去加載圖片。
2.加載圖片相關(guān)
a、加載多張圖片
對于加載多張圖片,我們一般會使用三級緩存來實現(xiàn)圖片的加載。內(nèi)存緩存、本地緩存、網(wǎng)絡(luò)緩存。緩存的目的是為了下一次加載速度更快。所以在內(nèi)存中保存著一定數(shù)量的圖片是有助于下一次圖片顯示的速度。但是,內(nèi)存中不能保存太多的圖片對象,所以我們使用LRUCache來保存內(nèi)存中的圖片,并且控制在一定的數(shù)量之內(nèi)。
b、加載單張圖片
這種情況是針對于某一張圖片特別大的情況。如果一張圖片非常非常大,如果50M,那么我只要一去加載它,那么我的程序肯定就會掛,根本還沒使用到三級緩存應(yīng)用程序就受不了了。所以,對于大圖片的顯示需要特殊處理。因為圖片雖然特別大,但是這個圖片所需要顯示的控件有可能是很小的。我們可以先把圖片的寬和高得到,再得到這張圖片所需要顯示的控件的寬高,就可以得到圖片和控件的縮放比例了。最后,根據(jù)縮放比例,設(shè)置圖片的采樣率,來減小單張圖片的內(nèi)存占用。
解決方案
無論是內(nèi)存泄露還是內(nèi)存溢出,最終的后果基本上是一致的,那就是造成應(yīng)用程序強行關(guān)閉。在應(yīng)用程序的功能開發(fā)完之后,怎么樣才能確定應(yīng)用程序有沒有內(nèi)存的問題呢?又怎樣來確定到底是哪一塊代碼出的問題呢?接下來我們就來說說關(guān)于內(nèi)存問題的解決方案。
1、使用monkey工具
因為有些內(nèi)存問題藏的比較深,要長期使用才能出現(xiàn)異常。所以可以使用monKey工具 來對我們的應(yīng)用程序進行壓力測試。
模擬200000次用戶操作的參考命令: adb shell monkey -s 23 -p cn.itcast.XXX(所測試的 包) --ignore-crashes --ignore-timeouts -v -v -v 200000 > D:\文件名.log
回車之后,我們所需要測試的應(yīng)用程序就啟動了,并且還有不斷觸摸事件被促發(fā),程序 生成的log會保存在D目錄下。
2、捕獲OOM異常
自定義Application并讓它實現(xiàn)UncaughtExceptionHandler 接口,在onCreate方法中讓自己成為系統(tǒng)的默認異常處理機制。Thread.setDefaultUncaughtExceptionHandler(this);
這樣子設(shè)置之后,如果應(yīng)用程序出現(xiàn)異常,就會調(diào)用Application中的uncaughtException 方法。我們就在這個方法中判斷異常是否為OOM異常。如果是OOM異常,就將內(nèi)存快照導(dǎo)到sd卡中去。
3.用第三方開源框架分析
leakcanary 是一個開源項目,一個內(nèi)存泄露自動檢測工具,是著名的 GitHub 開源組織 Square 貢獻的,它的主要優(yōu)勢就在于自動化過早的發(fā)覺內(nèi)存泄露、配置簡單、抓取貼心,缺點在于還存在一些bug,不過正常使用百分之九十情況是OK的,其核心原理與MAT工具類似。
轉(zhuǎn)載于:https://www.cnblogs.com/loaderman/p/6438371.html
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的内存泄漏和内存溢出的优化的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 梦到奇怪的动物是什么意思
- 下一篇: java集合框架05——ArrayLis