背景
今天發現有個App存在嚴重的內存泄漏問題,通過安裝LeakCanary找到大致方向,通過嚴格管理Timer、Presenter和Handler,輕松把它消耗的內容降低了100M。
過程
LeakCanary
LeakCanary是square公司提供的一個很好用的內存泄漏查找工具,與MAT不同的是,它會在你運行App時檢查你的內存回收,并找到內存泄漏點,直接提示給你,比用MAT去分析日志的形式更加簡單直接,不過缺點是LeakCanary的信息不像MAT那么多,很難查到更細微的問題。
LeakCanary的安裝過程很簡單,首先配置Gradle
然后在自定義的Application中初始化
記得在Manifest中使用自定義的Application
運行App,LeakCanary會在App運行過程中不定期收集信息,如果發現有內存泄漏的問題,就會在通知欄中給出提示,如XXXActivity大概因為XXX泄露了XXM內存,比如我們運行LeakCanary在Github上的demo,得到的提示就會像下圖這樣
可見,直接指出了問題類和問題原因,比起MAT還是簡單多了的
Timer
在檢查中,在Presenter里忘記關閉一個Timer的task,在Activity的onDestroy事件中調用Presenter的onDestroy
在Presenter的onDestroy中執行銷毀操作
我們看到,在Presenter銷毀元素時,做了三件事:
1.關閉線程(要先執行interrupt,否則休眠線程無法立即停止)
2.銷毀Presenter中關聯的View對象(否則會導致對應的Activity無法釋放)
3.退出Timer定時任務
Presenter
再看一遍Presenter執行銷毀操作的代碼
在上圖中,我們看到Presenter在銷毀時,一定要把View銷毀掉,否則會導致對應的Activity或Fragment無法釋放,在檢查中發現有些Presenter沒有寫銷毀,考慮在以后統一實現一個Presenter的基類,在基類中實現銷毀View的代碼
Handler
在檢查中,發現有些對話框彈出后無法回收內存,在檢查中發現了這樣的代碼
這是我們一般接觸到的典型的Handler寫法,但是注意看提示,提示中說明了這里有泄漏風險,從避免內存泄漏的角度,應該改成這樣的代碼
在這段寫法中,我們用static內部類去重載了一個Handler,static內部類實際上會生成一個弱引用對象,這就不會產生內存泄漏。
不過這樣一來,在static內部類中,我們就無法調用Activity的函數了,這就需要在這個Handler初始化時把Activity傳進來,直接傳進來的Activity還是可能造成內存泄漏,我們還要把它放到一個弱引用對象里,通過get()函數取得Activity對象并調用其函數。
總結
在這次的內存泄漏查找過程中,主要解決了三個問題
1.及時銷毀所有的Timer定時任務
2.及時銷毀Presenter中的View對象
3.檢查所有的Handler,改為static+弱引用的實現方式
解決這些問題后,再次運行LeakCanary,已經不再輸出內存泄漏的內容了,App消耗的內存也減少了100多M。
其實這里解決的內存泄漏問題都是很淺顯的部分,能在這些地方出錯,說明這個App的內存泄漏已經相當嚴重了,在日常編碼中,還是要多留一份心才是。