對(duì)于開發(fā)一個(gè)應(yīng)用程序來說,在前期完成主要功能之后,后期有一項(xiàng)非常重要的工作,那就是優(yōu)化應(yīng)用程序的內(nèi)存。而內(nèi)存優(yōu)化的方向主要是兩個(gè),一個(gè)是內(nèi)存溢出,另外一個(gè)就是內(nèi)存泄露。
內(nèi)存溢出和內(nèi)存泄露是兩個(gè)不同的概念。內(nèi)存溢出指的是內(nèi)存不夠用了,內(nèi)存的使用超過的系統(tǒng)規(guī)定的最大值。而內(nèi)存泄露指的是由于程序的邏輯錯(cuò)誤,導(dǎo)致一些沒有用的對(duì)象無法被垃圾回收器回收而一直占用著內(nèi)存。所以內(nèi)存泄露的堆積可能會(huì)造成內(nèi)存溢出。而內(nèi)存溢出不一定都是由內(nèi)存泄露引起的。這兩個(gè)概念要搞清楚。 ------------------Android培訓(xùn)學(xué)院
內(nèi)存溢出:
在安卓的應(yīng)用程序中,內(nèi)存溢出主要提要在幾個(gè)方面
1、ListView的顯示 我們?cè)谑褂肔istView的時(shí)候,都會(huì)給他設(shè)置Adapter,如果在Adapter中的getView方法 中,我們沒有復(fù)用convertView,就會(huì)造成在滑動(dòng)ListView的時(shí)候,會(huì)為每一個(gè)item都 生成一個(gè)View對(duì)象,而不管這個(gè)item之前是否已經(jīng)生成過View對(duì)象。如果來回滑動(dòng)的次數(shù)太多的話,就會(huì)造成View生成的數(shù)量太多,最終會(huì)造成內(nèi)存溢出。
如果ListView中的item是包含圖片的,那么,如果在快速滑動(dòng)的過程中,我們就去為 item加載圖片,此時(shí)非常容易造成內(nèi)存溢出。因?yàn)?,在快速滑?dòng)的過程中,垃圾回收器還來不及回收內(nèi)存,而新的item又需要新的內(nèi)存來顯示圖片。所以,在這種情況下,一般的做法是監(jiān)聽ListView的滾動(dòng)狀態(tài),當(dāng)ListView的滾動(dòng)狀態(tài)為空閑的情況下,里面 的每一個(gè)Item才去加載圖片。
2、加載圖片相關(guān) a、加載多張圖片
對(duì)于加載多張圖片,我們一般會(huì)使用三級(jí)緩存來實(shí)現(xiàn)圖片的加載。內(nèi)存緩存、本地緩存、網(wǎng)絡(luò)緩存。緩存的目的是為了下一次加載速度更快。所以在內(nèi)存中保存著一定數(shù)量的圖片是有助于下一次圖片顯示的速度。但是,內(nèi)存中不能保存太多的圖片對(duì)象,所以我們使用LRUCache來保存內(nèi)存中的圖片,并且控制在一定的數(shù)量之內(nèi)。
b、加載單張圖片
這種情況是針對(duì)于某一張圖片特別大的情況。如果一張圖片非常非常大,如果50M, 那么,我只要一去加載它,那么我的程序肯定就會(huì)掛,根本還沒使用到三級(jí)緩存應(yīng)用程序就受不了了。所以,對(duì)于大圖片的顯示需要特殊處理。因?yàn)閳D片雖然特別大,但是這個(gè)圖片所需要顯示的控件有可能是很小的。我們可以先把圖片的寬和高得到,再得到這張圖片所需要顯示的控件的寬高,就可以得到圖片和控件的縮放比例了。最后,根據(jù)縮放比例,設(shè)置圖片的采樣率,來減小單張圖片的內(nèi)存占用。
1. BitmapFactory.Options options = new BitmapFactory.Options();
2. //只得到圖片的大小,不去加載圖片的內(nèi)容
3. options.inJustDecodeBounds = true;
4. Bitmap boundBitmap = BitmapFactory.decodeFile(filePath,options);
5. int imageWidth = boundBitmap.getWidth();
6. int imageHeight = boundBitmap.getHeight();
7. //計(jì)算inSampleSize的值 此時(shí)可以根據(jù)控件大小和圖片大小來得到縮放比例 這里先寫成2
8. options.inSampleSize = 2;//圖片寬高都為原來的二分之一,即圖片為原來的四分之一
9. options.inJustDecodeBounds = false;
10. Bitmap bitmap = BitmapFactory.decodeFile(filePath,options);
內(nèi)存泄露:
內(nèi)存泄露是因?yàn)橛行?duì)象已經(jīng)沒有用了,但是卻無法被垃圾回收器回收內(nèi)存,導(dǎo)致這一塊內(nèi)存泄露了。隨著這類問題的堆積,泄露的內(nèi)存越來越大,可使用的內(nèi)存越來越小,直到內(nèi)存溢出。
而垃圾回收器無法回收某一些對(duì)象,是因?yàn)?,這些對(duì)象有人引用它了。所以內(nèi)存泄露的本質(zhì)就是,生命周期短的對(duì)象一直被生命周期長的對(duì)象引用,導(dǎo)致生命周期短的對(duì)象無法被垃圾回收器回收。
在安卓的應(yīng)用程序當(dāng)中,內(nèi)存泄露主要體現(xiàn)在幾個(gè)方面
1、注冊(cè)了之后忘了反注冊(cè) BraodcastReceiver,ContentObserver,F(xiàn)ileObserver在Activity onDestory或者某類聲明 周期結(jié)束之后一定要unregister掉,否則這個(gè)Activity會(huì)被system強(qiáng)引用,不會(huì)被內(nèi)存 回收。對(duì)于觀察者模式的寫法,在注冊(cè)某一些觀察者的時(shí)候,要既得在適當(dāng)?shù)臅r(shí)候,把 對(duì)應(yīng)的觀察者從觀察者列表中移除。否則存儲(chǔ)觀察者的集合列表會(huì)不斷的擴(kuò)大。
2、資源對(duì)象沒關(guān)閉 資源性對(duì)象比如Cursor,F(xiàn)ile文件等,在內(nèi)部的實(shí)現(xiàn)機(jī)制中都用了一些緩沖,所以在不 使用的時(shí)候這些資源對(duì)象的時(shí)候應(yīng)該及時(shí)關(guān)閉它們,這樣,他們的緩沖區(qū)域才能被既時(shí) 候回收。它們的緩沖不僅存在于Java虛擬機(jī)內(nèi),還存在于Java虛擬機(jī)外。如果我們僅 僅是把它的引用設(shè)置為null,而不關(guān)閉它們,往往會(huì)造成內(nèi)存泄露。我們需要在這些對(duì)象 不使用的情況下,立即調(diào)用close方法,然后再設(shè)置為null。
3、activity被集合引用導(dǎo)致activity不能釋放 一般我們?cè)趹?yīng)用程序中,做一個(gè)退出應(yīng)用程序的功能。當(dāng)點(diǎn)擊退出按鈕的時(shí)候,此時(shí)應(yīng) 該把這個(gè)應(yīng)用中所有activity都finish掉,這樣就可以退出這個(gè)應(yīng)用了。所以,我們需 要在每一個(gè)activity中的onCreate方法里,將當(dāng)前啟動(dòng)的activity保存在某一個(gè)集合列 表里面。這個(gè)集合列表可以存在于Application中的一個(gè)ArrayList。此時(shí),如果在activity 中的onDestory沒有將當(dāng)前的activity對(duì)象從集合列表中移除的話,那么就會(huì)造成activity 雖然退出了,但是這個(gè)activity的對(duì)象依然無法被系統(tǒng)回收,因?yàn)橛幸粋€(gè)集合一直引用 著這個(gè)activity對(duì)象。所以一定要記得在onDestory方法中把當(dāng)前的activity對(duì)象從集合 列表中移除。
4、Bitmap對(duì)象不在使用時(shí)調(diào)用recycle()釋放內(nèi)存 Bitmap非常的耗內(nèi)存。 多使用幾個(gè)Bitmap很可能一下子就會(huì)超過Java堆的限制。因此,在 用完Bitmap時(shí),要 及時(shí)的recycle掉。recycle并不能立即將Bitmap的內(nèi)存釋放,但是會(huì)在垃 圾回收器下一個(gè)工作的時(shí)機(jī)將這個(gè)bitmap回收。
解決方案
無論是內(nèi)存泄露還是內(nèi)存溢出,最終的后果基本上是一致的,那就是造成應(yīng)用程序強(qiáng)行關(guān)閉。在應(yīng)用程序的功能開發(fā)完之后,怎么樣才能確定應(yīng)用程序有沒有內(nèi)存的問題呢?又怎樣來確定到底是哪一塊代碼出的問題呢?接下來我們就來說說關(guān)于內(nèi)存問題的解決方案。
1、使用monkey工具 因?yàn)橛行﹥?nèi)存問題藏的比較深,要長期使用才能出現(xiàn)異常。所以可以使用monKey工具 來對(duì)我們的應(yīng)用程序進(jìn)行壓力測(cè)試。
模擬200000次用戶操作的參考命令: adb shell monkey -s 23 -p cn.itcast.XXX(所測(cè)試的 包) --ignore-crashes --ignore-timeouts -v -v -v 200000 > D:\文件名.log
回車之后,我們所需要測(cè)試的應(yīng)用程序就啟動(dòng)了,并且還有不斷觸摸事件被促發(fā),程序 生成的log會(huì)保存在D目錄下。
2、捕獲OOM異常 自定義Application并讓它實(shí)現(xiàn)UncaughtExceptionHandler 接口,在onCreate方法中讓 自己成為系統(tǒng)的默認(rèn)異常處理機(jī)制。Thread.setDefaultUncaughtExceptionHandler(this);
這樣子設(shè)置之后,如果應(yīng)用程序出現(xiàn)異常,就會(huì)調(diào)用Application中的uncaughtException 方法。我們就在這個(gè)方法中判斷異常是否為OOM異常。如果是OOM異常,就將內(nèi)存 快照導(dǎo)到sd卡中去。
這樣子之后,我們就可以在sd卡上找到發(fā)生異常時(shí)的內(nèi)存快照
3、分析內(nèi)存快照 使用MAT工具對(duì)內(nèi)存快照文件進(jìn)行分析。
在使用MAT工具打開內(nèi)存快照文件的時(shí)候,需要先將文件進(jìn)行轉(zhuǎn)換一下。因?yàn)镸AT只 能識(shí)別JAVA的內(nèi)存快照,而我們安卓的內(nèi)存快照和JAVA的內(nèi)存快照有點(diǎn)不太一樣,所 以需要進(jìn)行轉(zhuǎn)化。
例如:
接下來就是使用MAT工具打開轉(zhuǎn)化過后的內(nèi)存快照文件了。通過MAT工具可以看出這 個(gè)內(nèi)存快照有幾個(gè)對(duì)象,對(duì)象之間的引用關(guān)系是怎樣的。這樣對(duì)我們定位內(nèi)存問題非常 有幫助。
一般在應(yīng)用程序開發(fā)的后期會(huì)使用這三個(gè)步驟來增強(qiáng)應(yīng)用程序的健壯性。
本文版權(quán)歸黑馬程序員Android+物聯(lián)網(wǎng)培訓(xùn)學(xué)院所有,歡迎轉(zhuǎn)載,轉(zhuǎn)載請(qǐng)注明作者出處。謝謝!作者:黑馬程序員Android+物聯(lián)網(wǎng)培訓(xùn)學(xué)院首發(fā):http://android.itheima.com