檢測(cè)內(nèi)存泄漏的工具有LeakCanary熬尺、MAT等工具摸屠。
一、內(nèi)存泄漏的原因:
當(dāng)一個(gè)對(duì)象已經(jīng)不需要使用粱哼,本該被回收季二,而另一個(gè)正在使用的對(duì)象持有它的引用,導(dǎo)致不能被回收揭措,而停留在堆內(nèi)存中胯舷,產(chǎn)生了內(nèi)存泄漏。通常是Activity或者Fragment的泄露绊含。
二桑嘶、內(nèi)存泄漏對(duì)程序的影響:
內(nèi)存泄漏是造成應(yīng)用程序OOM的主要原因之一。系統(tǒng)為每個(gè)應(yīng)用程序分配的內(nèi)存有限躬充,當(dāng)一個(gè)應(yīng)用中產(chǎn)生的內(nèi)存泄漏比較多時(shí)逃顶,難免會(huì)導(dǎo)致應(yīng)用所需要的內(nèi)存超過(guò)系統(tǒng)分配的內(nèi)存限額,造成內(nèi)存溢出而導(dǎo)致crash充甚。
三以政、常見的內(nèi)存泄漏:
1、單例造成的內(nèi)存泄漏
由于單例的靜態(tài)特性使得單例的生命周期和應(yīng)用的生命周期一樣長(zhǎng)津坑,如果一個(gè)對(duì)象已經(jīng)不使用妙蔗,而單例對(duì)象還持有該對(duì)象的引用,導(dǎo)致不能正常被回收疆瑰,發(fā)生內(nèi)存泄漏眉反。
由于需要傳入Context,這個(gè)Context的生命周期長(zhǎng)短至關(guān)重要穆役。
1)寸五、傳入的是Application的Context,這將沒(méi)有任何問(wèn)題耿币,因?yàn)閱卫纳芷诤虯pplication的一樣長(zhǎng)梳杏。
2)、傳入的是Activity的Context淹接。當(dāng)這個(gè)Context所對(duì)應(yīng)的Activity退出時(shí)十性,由于該Context和Activity的生命周期一樣長(zhǎng),當(dāng)前的Activity退出時(shí)它的內(nèi)存并不會(huì)被回收塑悼,因?yàn)閱卫龑?duì)象持有該Activity的引用劲适。
正確的單例應(yīng)用:
不管傳入什么Context最終使用的Application的Context。
2厢蒜、非靜態(tài)內(nèi)部類創(chuàng)建靜態(tài)實(shí)例造成的內(nèi)存泄漏霞势。
非靜態(tài)內(nèi)部類默認(rèn)會(huì)持有外部類的引用烹植,而又使用該非靜態(tài)內(nèi)部類創(chuàng)建了一個(gè)靜態(tài)的實(shí)例,該實(shí)例的生命周期和應(yīng)用一樣長(zhǎng)愕贡,導(dǎo)致該靜態(tài)實(shí)例會(huì)一直持有該Activity的引用草雕,導(dǎo)致Activity的內(nèi)存資源不能正常回收固以。
正確的做法是:將該內(nèi)部類設(shè)為靜態(tài)內(nèi)部類或?qū)⒃搩?nèi)部類變成一個(gè)單例墩虹。
3、Handler造成的內(nèi)存泄漏嘴纺。
由于mHandler是Handler的非靜態(tài)匿名內(nèi)部類的實(shí)例败晴。所以它持有外部類Activity的引用。消息隊(duì)列是在一個(gè)Looper線程中不斷輪詢處理消息栽渴,當(dāng)這個(gè)Activity退出時(shí)消息隊(duì)列中還有未處理的消息或者正在處理的消息尖坤,而消息隊(duì)列中的Message持有mHandler實(shí)例的引用,mHandler又持有Activity的引用闲擦,導(dǎo)致該Activity的內(nèi)存資源無(wú)法及時(shí)回收慢味。
正確的做法是:
創(chuàng)建一個(gè)靜態(tài)Handler內(nèi)部類,然后對(duì)Handler持有的對(duì)象使用弱引用墅冷,這樣在回收時(shí)也可以回收Handler持有的對(duì)象纯路。在Activity的Destroy時(shí)或者Stop時(shí)應(yīng)該移除消息隊(duì)列中的消息。使用mHandler.removeCallbacksAndMessages(null);是移除消息隊(duì)列中所有消息和所有的Runnable寞忿。當(dāng)然也可以使用mHandler.removeCallbacks();或mHandler.removeMessages();來(lái)移除指定的Runnable和Message驰唬。
4、線程造成的內(nèi)存泄漏腔彰。
上訴都是匿名內(nèi)部類叫编,對(duì)當(dāng)前Activity都有一個(gè)隱式引用。如果在Activity銷毀之前霹抛,任務(wù)還未完成搓逾,那么將導(dǎo)致Activity的內(nèi)存資源無(wú)法回收,造成內(nèi)存泄漏杯拐。
正確做法還是使用靜態(tài)內(nèi)部類的方式霞篡。
5、資源未關(guān)閉造成的內(nèi)存泄漏端逼。
對(duì)于使用BroadcastReceiver朗兵、ContentObserver、File顶滩、Cursor矛市、Stream、Bitmap等诲祸,應(yīng)該在Activity銷毀時(shí)及時(shí)關(guān)閉或者注銷浊吏,否則這些資源不會(huì)被回收,造成內(nèi)存泄漏救氯。
6找田、靜態(tài)的View
有時(shí),當(dāng)一個(gè)Activity經(jīng)常啟動(dòng)着憨,但是對(duì)應(yīng)的View讀取非常耗時(shí)墩衙,我們可以通過(guò)靜態(tài)View變量來(lái)保持對(duì)該Activity的rootView引用。這樣就可以不用每次啟動(dòng)Activity都去讀取并渲染View了甲抖。這確實(shí)是一個(gè)提高Activity啟動(dòng)速度的好方法漆改!但是要注意,一旦View attach到我們的Window上准谚,就會(huì)持有一個(gè)Context(即Activity)的引用挫剑。而我們的View有事一個(gè)靜態(tài)變量,所以導(dǎo)致Activity不被回收柱衔。
解決辦法:
??在使用靜態(tài)View時(shí)樊破,需要確保在資源回收時(shí),將靜態(tài)View detach掉唆铐。
7哲戚、監(jiān)聽器(各種需要注冊(cè)的Listener,Watcher等)
例如:EditText的一個(gè)addTextChangeListener艾岂,如果在回調(diào)方法里有耗時(shí)操作顺少,可能會(huì)造成內(nèi)存泄露。
解決辦法:
在onDestory時(shí)王浴,取消注冊(cè)脆炎,editText.removeTextChangedListener。
8叼耙、屬性動(dòng)畫
在使用ValueAnimator或者ObjectAnimator腕窥,如果沒(méi)有及時(shí)做cancel取消動(dòng)畫,就可能造成內(nèi)存泄漏筛婉。
解決辦法:在onDestory()調(diào)用動(dòng)畫的cancel()方法簇爆。cancel()方法最后調(diào)用了endAnimation()。
9爽撒、RxJava
在使用RxJava入蛆,如果發(fā)布一個(gè)訂閱后,由于沒(méi)有及時(shí)取消硕勿,導(dǎo)致Activity/Fragment無(wú)法銷毀哨毁,導(dǎo)致內(nèi)存泄漏源武。
解決方法:Android架構(gòu)中添加AutoDispose解決RxJava內(nèi)存泄漏
https://blog.csdn.net/mq2553299/article/details/79418068
10扼褪、WebView
在Android5.1及以上版本,webView可能存在內(nèi)存泄漏话浇。
解決辦法:在銷毀webview前一定要onDetachedFromWindow。先將webview從它的父view中移除再調(diào)用onDestory方法
Android5.1WebView內(nèi)存泄漏及解決:https://blog.csdn.net/u013085697/article/details/53259116
11幔崖、其他系統(tǒng)控件以及自定義View
在 Android Lollipop 之前使用 AlertDialog 可能會(huì)導(dǎo)致內(nèi)存泄漏:
https://blog.csdn.net/u012464435/article/details/50774580
Dialog和DialogFragment在Android5.0以下的內(nèi)存泄漏:
https://www.cnblogs.com/endure/p/7664320.html
View的post方法導(dǎo)致的內(nèi)存泄漏: