什么是內(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)申請(qǐ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)清除堆中我們不再使用的對(duì)象的催蝗。當(dāng)然了,在java中對(duì)象是通過引用來使用的育特。但是如果再也沒有引用指向?qū)ο蟮脑挶牛敲催@個(gè)對(duì)象就無從處理,無從調(diào)用。在java中我們稱這種對(duì)象為不可到達(dá)對(duì)象犬缨。簡單來說喳魏,此對(duì)象在內(nèi)存中的申請(qǐng)的空間我們無法回收,有對(duì)象的強(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)部類隱式持有外部類對(duì)象。簡單來說當(dāng)非靜態(tài)內(nèi)部類的對(duì)象的生命周期比外部類對(duì)象生命周期長就漾,就會(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的接口對(duì)象纯陨,model也很有可能擁有者presenter實(shí)例。所以當(dāng)activity銷毀的時(shí)候,model還在獲取著數(shù)據(jù)翼抠。Presenter也就一直持有著view對(duì)象咙轩。這條gc鏈不間斷,activity就無法正常的回收阴颖。
解決辦法:
1)在actity的ondestory方法中利用presenter層進(jìn)行資源釋放活喊,解除和view層的綁定,并且取消model層的網(wǎng)絡(luò)請(qǐng)求量愧。最后置空presenter層钾菊。
2)將presenter層轉(zhuǎn)化為弱引用去引用view對(duì)象
4.RxJava也會(huì)引起內(nèi)存泄漏。內(nèi)存泄漏產(chǎn)生的根本原因偎肃,當(dāng)一個(gè)對(duì)象處于可以被回收狀態(tài)時(shí)煞烫,卻因?yàn)樵搶?duì)象被其他暫時(shí)不可被回收的對(duì)象持有引用,而導(dǎo)致不能被回收累颂,如此一來滞详,該對(duì)象所占用的內(nèi)存被回收以作他用,這部分內(nèi)存就算是被泄露掉了紊馏。簡單來說料饥,就是該丟掉的垃圾還占著有用的空間沒有被及時(shí)丟掉。
解決辦法:
1)使用取消訂閱管理器朱监,compositeSubscription.讓訂閱管理器統(tǒng)一管理持有所有請(qǐ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方法中獲得檢測對(duì)象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);
通過獲得到檢測對(duì)象RefWatcher不铆,將我們需要檢測的對(duì)象傳遞給它的watch()方法就可以了蝌焚。
當(dāng)然還有其他檢測方法,比如MAT等等誓斥,具體使用什么方法還是要看個(gè)人本身或者項(xiàng)目中的實(shí)際需求只洒,不可盲目使用。
到這里關(guān)于Android的內(nèi)存泄漏常見的原因和檢測方法就講述結(jié)束了劳坑,關(guān)于內(nèi)存泄漏其實(shí)還有很多我們還未了解到的知識(shí)毕谴,這些書本是不會(huì)交給我們的,我們需要實(shí)際的去體驗(yàn)距芬,在項(xiàng)目中碰到涝开,我們才能夠?qū)?nèi)存泄漏有更精進(jìn)的了解,當(dāng)然如果項(xiàng)目中沒有任何內(nèi)存泄漏框仔,那肯定是天大的好事舀武。以上我說的如果不對(duì)的地方,歡迎各位提出寶貴的意見离斩,一起交流银舱,共同進(jìn)步瘪匿。