Android常見的內(nèi)存泄漏出現(xiàn)原因的分析及檢查辦法

什么是內(nèi)存泄漏?

內(nèi)存泄漏(Memory Leak)是指程序中己動(dòng)態(tài)分配的堆內(nèi)存由于某種原因程序未釋放或無法釋放,造成系統(tǒng)內(nèi)存的浪費(fèi),導(dǎo)致程序運(yùn)行速度減慢甚至系統(tǒng)崩潰等嚴(yán)重后果。

很多人會(huì)把內(nèi)存泄漏和內(nèi)存溢出混淆,其實(shí)兩者并不是同一個(gè)概念,但是兩者卻有非常重要的聯(lián)系,簡單來說大量的內(nèi)存泄漏就會(huì)導(dǎo)致內(nèi)存溢出。下面貼出內(nèi)存溢出的概念。

內(nèi)存溢出:程序向系統(tǒng)申請的內(nèi)存空間超出了系統(tǒng)能給的。

比如內(nèi)存只能分配一個(gè)int類型,我卻要塞給他一個(gè)long類型,系統(tǒng)就出現(xiàn)oom。簡單來說就是房子就那么大,但是越來越多的人進(jìn)來,直到房子已經(jīng)裝不下這么多人,人還在往房子里面走,就會(huì)導(dǎo)致房子越來越擁擠,直到將房子擠爆。

我們理解了內(nèi)存泄漏和內(nèi)存溢出的區(qū)別之后。那么我們就來看看導(dǎo)致我們程序內(nèi)存泄漏的根本原因是什么。


暗中觀察一番之后,我們來看內(nèi)存泄漏出現(xiàn)的根本原因。首先我們都知道java是有垃圾回收機(jī)制的。也就是我們常說的gc。gc是可以自動(dòng)清除堆中我們不再使用的對象的。當(dāng)然了,在java中對象是通過引用來使用的。但是如果再也沒有引用指向?qū)ο蟮脑挘敲催@個(gè)對象就無從處理,無從調(diào)用。在java中我們稱這種對象為不可到達(dá)對象。簡單來說,此對象在內(nèi)存中的申請的空間我們無法回收,有對象的強(qiáng)引用,且沒有及時(shí)釋放,進(jìn)而造成內(nèi)存單元一直被占用,浪費(fèi)空間,就可能造成內(nèi)存溢出!

下面我們總結(jié)一下安卓內(nèi)存泄漏出現(xiàn)的原因,常見內(nèi)存泄漏的匯總。


1.非靜態(tài)內(nèi)部類或者匿名內(nèi)部類隱式持有外部類對象。簡單來說當(dāng)非靜態(tài)內(nèi)部類的對象的生命周期比外部類對象生命周期長,就會(huì)引起內(nèi)存泄漏。安卓比較典型場景就是使用handler.

也就是說當(dāng)handler正在處理消息的時(shí)候,用戶退出activity,但是這個(gè)時(shí)候handler還在處理消息。導(dǎo)致activity無法被回收。也就會(huì)發(fā)生內(nèi)存泄漏。

解決辦法:

1)將handler使用static修飾

2)handler通過弱引用的方式持有activity

3)在activity的ondestory生命周期中將handler中的消息置空

2.單例模式也會(huì)引起內(nèi)存泄漏。我們使用單例模式是希望全局只有一個(gè)靜態(tài)變量,如果我們傳入了上下文的話,activity是間接繼承上下文的。所以這個(gè)時(shí)候我們要是將activity退出,應(yīng)該是回收activity的,但是單例模式還持有著它的引用,導(dǎo)致activity回收失敗,造成內(nèi)存泄漏。

解決辦法:

1)不管外面?zhèn)魅胧裁瓷舷挛模覀儐卫J嚼锩娑冀o它轉(zhuǎn)化為application的context,這樣單例模式的生命周期就和應(yīng)用一樣長,避免了內(nèi)存泄漏。

3.mvp框架引起內(nèi)存泄漏。Mvp框架優(yōu)點(diǎn)很多,包括高度解耦,代碼復(fù)讀性強(qiáng)等等,但是它也有缺點(diǎn)。缺點(diǎn)之一就是容易造成內(nèi)存泄漏。Presenter層持有著view的接口對象,model也很有可能擁有者presenter實(shí)例。所以當(dāng)activity銷毀的時(shí)候,model還在獲取著數(shù)據(jù)。Presenter也就一直持有著view對象。這條gc鏈不間斷,activity就無法正常的回收。

解決辦法:

1)在actity的ondestory方法中利用presenter層進(jìn)行資源釋放,解除和view層的綁定,并且取消model層的網(wǎng)絡(luò)請求。最后置空presenter層。

2)將presenter層轉(zhuǎn)化為弱引用去引用view對象

4.RxJava也會(huì)引起內(nèi)存泄漏。內(nèi)存泄漏產(chǎn)生的根本原因,當(dāng)一個(gè)對象處于可以被回收狀態(tài)時(shí),卻因?yàn)樵搶ο蟊黄渌麜簳r(shí)不可被回收的對象持有引用,而導(dǎo)致不能被回收,如此一來,該對象所占用的內(nèi)存被回收以作他用,這部分內(nèi)存就算是被泄露掉了。簡單來說,就是該丟掉的垃圾還占著有用的空間沒有被及時(shí)丟掉。

解決辦法:

1)使用取消訂閱管理器,compositeSubscription.讓訂閱管理器統(tǒng)一管理持有所有請求,統(tǒng)一取消。

2)使用Rxlifecycle第三方庫,完成發(fā)布事件與當(dāng)前組件進(jìn)行綁定,實(shí)現(xiàn)生命周期同步,組件生命周期結(jié)束后,自動(dòng)取消訂閱。

3) 自己取消訂閱,調(diào)用unsubscribe()方法

5.timer和timertask(屬性動(dòng)畫)引起的內(nèi)存泄漏,因?yàn)槲覀兺ǔ?huì)用來做一些計(jì)時(shí)操作或者循環(huán)操作,如果忘記銷毀變量的話,那么timer或者timertask可能會(huì)一直持有著activity或者其他變量,造成內(nèi)存泄漏。屬性動(dòng)畫和上述的問題是一樣的,所以在這里就集中地說了。

解決辦法:

1)在適當(dāng)?shù)臅r(shí)機(jī)調(diào)用cancel()方法(比如在activity的ondestory方法里面調(diào)用cancel方法)

6.關(guān)于webview的內(nèi)存泄漏,是因?yàn)閣ebview加載網(wǎng)頁后長期占用內(nèi)存而不能釋放,也就是說webview持有著acitivity變量,導(dǎo)致占用的內(nèi)存始終無法釋放,就算是調(diào)用了webview.ondestory()也不能解決問題。

解決方法:

1)當(dāng)然了,也有最終的解決方案。在webview銷毀之前需要先從父容器中將webview移除。然后在調(diào)用webview的銷毀方法。

Android中檢查內(nèi)存泄漏的方法有很多種,我們這里就介紹平時(shí)我們最常用的方法。

1.利用Android Studio自帶工具進(jìn)行內(nèi)存泄漏的檢測。首先我們先打開Android Studio的控制臺(tái)(logcat),然后找到monitors,打開。我們就可以看到下面這張圖這樣。

?????? 當(dāng)我們連接模擬器運(yùn)行項(xiàng)目的時(shí)候,我們就可以通過自帶工具看到我們的內(nèi)存使用情況,當(dāng)然還有其他的一些功能,(cpu的消耗情況,網(wǎng)絡(luò)測速,Gpu的繪制情況)。我們都可以在這里看到。如果發(fā)生內(nèi)存泄漏或者內(nèi)存溢出的話我們就可以發(fā)現(xiàn),但是這種方法不全面,必須要我們關(guān)注它的內(nèi)存消耗狀況。不夠方便,我們需要的是如果有內(nèi)存泄漏的話,能夠第一時(shí)間的通知我們?nèi)ソ鉀Q,并且將發(fā)生內(nèi)存泄漏的位置告訴我們。如果這樣的話,這種方式就不能滿足我們的需求了。這個(gè)時(shí)候我們就需要另外一種方法了。

2.利用Leakcanary來檢查我們項(xiàng)目中出現(xiàn)的內(nèi)存泄漏。

leakcanary是square公司出的一個(gè)第三方檢查內(nèi)存泄漏的工具,在這個(gè)工具出現(xiàn)之前,square公司的技術(shù)人員也被內(nèi)存泄漏的問題困擾了很久,當(dāng)時(shí)他們想要利用一種思路,一種方法,徹底解決內(nèi)存泄漏的問題。但是后來失敗了。他們發(fā)現(xiàn)他們距離解決問題的方向更遙遠(yuǎn)了。后來及時(shí)的調(diào)整思路,這才有了leakcanary。所以說并不是大公司的技術(shù)人員不會(huì)被這些問題困擾,人和人都是一樣的。區(qū)分的只是人與人的耐心程度。

下面來介紹一下這個(gè)工具具體是怎么使用的。因?yàn)槭堑谌焦ぞ撸晕覀冃枰獙?dǎo)入兩個(gè)依賴。一個(gè)是debug,一個(gè)是release

debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3'

releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3'

通常來說,我們需要使用這個(gè)工具能夠檢測整個(gè)項(xiàng)目中的內(nèi)存泄漏問題,所以我還是建議抽取一個(gè)app類,在這個(gè)類中的oncreate方法中獲得檢測對象RefWatcher。

refWatcher= LeakCanary.install(this);

然后通過application類傳遞出去。具體方法如下:

public static RefWatcher getRegwatcher(Context context){

MyApp myApp = (MyApp) context.getApplicationContext();

return myApp.refWatcher;

}

然后我們就可以使用了,通過兩行代碼的調(diào)用,我們就可以實(shí)現(xiàn)實(shí)時(shí)的檢測我們項(xiàng)目中是否出現(xiàn)了內(nèi)存泄漏。如果我們想要在MainActivity中檢測內(nèi)存泄漏,我們應(yīng)該具體怎么寫呢?具體代碼如下:

RefWatcher regwatcher = MyApp.getRegwatcher(this.getApplicationContext());

regwatcher.watch(this);

通過獲得到檢測對象RefWatcher,將我們需要檢測的對象傳遞給它的watch()方法就可以了。

當(dāng)然還有其他檢測方法,比如MAT等等,具體使用什么方法還是要看個(gè)人本身或者項(xiàng)目中的實(shí)際需求,不可盲目使用。

到這里關(guān)于Android的內(nèi)存泄漏常見的原因和檢測方法就講述結(jié)束了,關(guān)于內(nèi)存泄漏其實(shí)還有很多我們還未了解到的知識,這些書本是不會(huì)交給我們的,我們需要實(shí)際的去體驗(yàn),在項(xiàng)目中碰到,我們才能夠?qū)?nèi)存泄漏有更精進(jìn)的了解,當(dāng)然如果項(xiàng)目中沒有任何內(nèi)存泄漏,那肯定是天大的好事。以上我說的如果不對的地方,歡迎各位提出寶貴的意見,一起交流,共同進(jìn)步。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容