內(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è)。