內(nèi)存泄露就是指該被GC垃圾回收的募疮,由于有另外一個(gè)對(duì)象仍然在引用它,導(dǎo)致無(wú)法回收僻弹,造成內(nèi)存泄露阿浓,過(guò)多的內(nèi)存泄露會(huì)導(dǎo)致OOM。
一蹋绽、原因及優(yōu)化
1 非靜態(tài)內(nèi)部類(lèi)芭毙、匿名內(nèi)部類(lèi)
非靜態(tài)內(nèi)部類(lèi)、匿名內(nèi)部類(lèi) 都會(huì)持有外部類(lèi)的一個(gè)引用卸耘,如果有一個(gè)靜態(tài)變量引用了非靜態(tài)內(nèi)部類(lèi)或者匿名內(nèi)部類(lèi)退敦,導(dǎo)致非靜態(tài)內(nèi)部類(lèi)或者匿名內(nèi)部類(lèi)的生命周期比外部類(lèi)(Activity)長(zhǎng),就會(huì)導(dǎo)致外部類(lèi)在該被回收的時(shí)候鹊奖,無(wú)法被回收掉苛聘,引起內(nèi)存泄露,除非外部類(lèi)被卸載(JVM自帶的類(lèi)加載器所加載的類(lèi)忠聚,在虛擬機(jī)的生命周期中设哗,始終不會(huì)被卸載,除非使用自定義的類(lèi)加載器)两蟀。
優(yōu)化:
- 將非靜態(tài)內(nèi)部類(lèi)网梢、匿名內(nèi)部類(lèi) 改成靜態(tài)內(nèi)部類(lèi),或者直接抽離成一個(gè)外部類(lèi)赂毯。
- 如果在靜態(tài)內(nèi)部類(lèi)中战虏,需要引用外部類(lèi)對(duì)象拣宰,那么可以將這個(gè)引用封裝在一個(gè)WeakReference中。
2 Handler
主線程的Looper對(duì)象不斷從消息隊(duì)列中取出消息烦感,然后再交給Handler處理巡社。如果在Activity中定義Handler對(duì)象,那么Handler肯定持有Activty的引用手趣,而每個(gè)Message對(duì)象是持有Handler的引用的(Message對(duì)象的target屬性持有Handler引用)晌该,從而導(dǎo)致Message間接引用到了Activity。如果在Activty destroy之后绿渣,消息隊(duì)列中還有Message對(duì)象朝群,Activty是不會(huì)被回收的。當(dāng)然了中符,如果消息正在準(zhǔn)備(處于延時(shí)入隊(duì)期間)放入到消息隊(duì)列中也是一樣的姜胖。
優(yōu)化:
- 將Handler放入單獨(dú)的類(lèi)或者將Handler放入到靜態(tài)內(nèi)部類(lèi)中(靜態(tài)內(nèi)部類(lèi)不會(huì)持有外部類(lèi)的引用)。如果想要在Handler內(nèi)部去調(diào)用所在的外部類(lèi)Activity淀散,可以在Handler內(nèi)部使用弱引用的方式指向所在Activity右莱,這樣不會(huì)導(dǎo)致內(nèi)存泄漏。
- 在onDestory時(shí)档插,調(diào)用相應(yīng)的remove方法移除回調(diào)和刪除消息隧出。
弱引用相關(guān)請(qǐng)移步:Android強(qiáng)引用、弱引用阀捅、軟引用
3 靜態(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不被回收。
優(yōu)化:
- 在使用靜態(tài)View時(shí)朴肺,需要確保在資源回收時(shí)窖剑,將靜態(tài)View detach掉。
4 監(jiān)聽(tīng)器(各種需要注冊(cè)的Listener戈稿、Watcher等)
當(dāng)需要使用系統(tǒng)服務(wù)(比如執(zhí)行某些后臺(tái)任務(wù)西土、為硬件訪問(wèn)提供接口等等系統(tǒng)服務(wù))時(shí),需要把Activity自己注冊(cè)到服務(wù)的監(jiān)聽(tīng)器中鞍盗,這會(huì)讓服務(wù)持有Activity的引用需了,如果沒(méi)有在Activity銷(xiāo)毀時(shí)取消注冊(cè)跳昼,那就會(huì)導(dǎo)致Activity泄漏。
例如:EditText的addTextChangeListener肋乍,如果在回調(diào)方法里有耗時(shí)操作鹅颊,可能會(huì)造成內(nèi)存泄露。
優(yōu)化:
- 在onDestory時(shí)墓造,取消注冊(cè)挪略。
比如:editText.removeTextChangedListener
5 WebView
在android 5.1及以上版本的代碼中,WebView可能會(huì)存在內(nèi)存泄露滔岳。
(原因可以參考這篇文章:https://blog.csdn.net/u013085697/article/details/53259116)
優(yōu)化:
- 在銷(xiāo)毀WebView前一定要onDetachedFromWindow,先將WebView從它的父View中移除再調(diào)用destroy方法挽牢。
6 單例造成的內(nèi)存泄漏
單例的靜態(tài)特性使得單例的生命周期和應(yīng)用的生命周期一樣長(zhǎng)谱煤,這就說(shuō)明了如果一個(gè)對(duì)象已經(jīng)不需要使用了,而單例對(duì)象還持有該對(duì)象的引用禽拔,那么這個(gè)對(duì)象將不能被正沉趵耄回收,這就導(dǎo)致了內(nèi)存泄漏睹栖。
7 線程死鎖
8 資源對(duì)象沒(méi)有關(guān)閉
當(dāng)打開(kāi)資源時(shí)硫惕,一般都會(huì)使用緩存。比如讀寫(xiě)文件資源野来、打開(kāi)數(shù)據(jù)庫(kù)資源恼除、使用Bitmap資源等等。當(dāng)不再使用時(shí)曼氛,應(yīng)該關(guān)閉它們豁辉,使得緩存內(nèi)存區(qū)域及時(shí)回收。雖然有些對(duì)象舀患,如果不去關(guān)閉徽级,它自己在finalize()函數(shù)中會(huì)自行關(guān)閉,但是這要等到GC回收時(shí)才關(guān)閉聊浅,這樣會(huì)導(dǎo)致緩存駐留一段時(shí)間餐抢。如果頻繁打開(kāi)資源,內(nèi)存泄漏帶來(lái)的影響就比較明顯了低匙。
9 屬性動(dòng)畫(huà)
在使用ValueAnimator或者ObjectAnimator時(shí)旷痕,如果沒(méi)有及時(shí)做cancel取消動(dòng)畫(huà),就可能造成內(nèi)存泄露顽冶。
因?yàn)樵赾ancel方法里苦蒿,最后調(diào)用了endAnimation(); ,在endAnimation里渗稍,有個(gè)AnimationHandler的單例佩迟,會(huì)持有屬性動(dòng)畫(huà)對(duì)象的引用团滥。
優(yōu)化:
- 在onDestory時(shí),調(diào)用動(dòng)畫(huà)的cancel方法
10 其他的系統(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秉溉,http://www.mamicode.com/info-detail-1753936.html力惯。
11 TimerTask
三、檢測(cè)工具
1 命令行查看內(nèi)存占用情況
adb shell dumpsys meminfo -a 包名
2 AS自帶的Monitors
展示內(nèi)存使用召嘶,及網(wǎng)絡(luò)父晶、CPU、GPU使用情況弄跌。
3 LeakCanary
隨應(yīng)用運(yùn)行甲喝,實(shí)時(shí)監(jiān)測(cè)。