文章出處

android常見內存泄漏主要有以下幾類:

一、Handler 引起的內存泄漏。

在Android開發中,我們經常會使用Handler來控制主線程UI程序的界面變化,使用非常簡單方便,但是稍不注意,很容易引發內存泄漏。

我們知道,Handler、Message、MessageQueue是相互關聯在一起的,Handler通過發送消息Message與主線程進行交互,如果Handler發送的消息Message尚未被處理,該Message及發送它的Handler對象將被MessageQueue一直持有,這樣就可能會導致Handler無法被回收。

請看下面的代碼:

SecondActivity代碼中有一個延遲1秒執行的消息Message,當界面從SecondActivity跳轉到ThirdActivity時,SecondActivity自動進入后臺,此時如果系統資源緊張(或者打開設置里面的“不保留活動”選項),SecondActivity將會被finish。但問題來了,由于SecondActivity的Handler對象mHandler為非靜態匿名內部類對象,它會自動持有外部類SecondActivity的引用,從而導致SecondActivity無法被回收,造成內存泄漏。
解決辦法:將Handler聲明為靜態內部類,就不會持有外部類SecondActivity的引用,其生命周期就和外部類無關,如果Handler里面需要context的話,可以通過弱引用方式引用外部類。參考代碼如下:

通過上面的方法,創建一個靜態Handler內部類,其持有的對象context使用弱引用,可以避免SecondActivity內存泄漏,但是Looper線程的消息隊列中可能還有待處理的消息,所以在Activity的onDestroy方法中,還要記住移除消息隊列中待處理的消息。參考代碼如下:

二、單例模式引起的內存泄漏
由于單例的生命周期是和app的生命周期一致的,如果使用不當很容易引發內存泄漏。如下代碼:

這是一個單例模式的標準寫法,表面上看沒有任何問題,但是細心的同學會發現,構建該單例的一個實例時需要傳入一個Context,此時傳入的Context就非常關鍵,如果此時傳入的是Activity,由于Context會被創建的實例一直持有,當Activity進入后臺或者開啟設置里面的不保留活動時,Activity會被銷毀,但是單例持有它的Context引用,Activity又沒法銷毀,導致了內存泄漏。
如果此時傳入的Context是ApplicationContext,由于ApplicationContext的生命周期是和app一致的,不會導致內存泄漏。但是我們不能指望使用這個單例的用戶始終傳入期望的Context,因此需要對這個單例設計進行調整,可以在構造函數中對mContext賦值改為this.mContext = context.getApplicationContext;當然,也可以直接不讓用戶傳入context。
參考解決辦法:
1一般在我們開發的應用中,都會實現Application,在里面做一些全局性的事情。可以在該實現里面對外提供一個單例,通過此實例來獲取ApplicationContext。代碼如下:



2、重構Singleton,把構建單例時的context去掉,避免外面使用的人傳入錯誤參數,代碼如下:

三、非靜態內部類創建靜態實例引起的內存泄漏
請看下面的代碼:

上述代碼中,SecondActivity2包含一個內部類InnerClass,并且在onCreate代碼中創建了InnerClass的靜態實例mInner,該實例和app的生命周期是一致的。在某些場景,如Activity需要頻繁切換,需要不斷加載大量圖片的場合,是會出現上述代碼的,每次Activity啟動之后都會使用該單例,避免重復一些有壓力的操作。但是這樣會引起內存泄漏,因為非靜態的內部類InnerClass會自動持有外部類SecondActivity2的引用,創建的靜態實例mInner就會一直持有SecondActivity2的引用,導致SecondActivity2需要銷毀的時候沒法正常銷毀。
怎么知道靜態實例mInner持有SecondActivity2的引用呢?debug程序之后你會清晰的發現靜態實例mInner確實持有外部類SecondActivity2的引用,見下圖:
上述代碼的正確做法是把內部類InnerClass修改為靜態的就可以避免內存泄漏了,因為靜態內部類InnerClass不在持有外部類SecondActivity2的引用了。見下圖:
當然,也可以把InnerClass單獨抽出來作為一個內,寫成單例模式,完成同樣的功能,同時也可以避免內存泄漏。
四、非靜態匿名內部類引起的內存泄漏
在android開發中,相信大家都會不知不覺地用到大量匿名內部類,如接受廣播、點擊事件、Handler消息處理等等。但是要注意,如果匿名內部類被異步線程使用,可能會引起內存泄漏。請看如下代碼:

上述代碼中,mRunnable 是非靜態匿名內部類,會自動持有外部類SecondActivity3的引用,但是mRunnable被異步線程Thread使用,這樣就會導致SecondActivity3在銷毀的時候沒法正常銷毀,從而引起內存泄漏。
正確的做法應該是把mRunnable設置為靜態的,這樣就不會自動持有外部類SecondActivity3的引用,也就不會引起內存泄漏了。
五、注冊/反注冊未成對使用引起的內存泄漏
在andorid開發中,我們經常會在Activity的onCreate中注冊廣播接受器、EventBus等,如果忘記成對的使用反注冊,可能會引起內存泄漏。開發過程中應該養成良好的相關,在onCreate或onResume中注冊,要記得相應的在onDestroy或onPause中反注冊。
六、資源對象沒有關閉引起的內存泄漏
在android中,資源性對象比如Cursor、File、Bitmap、視頻等,系統都用了一些緩沖技術,在使用這些資源的時候,如果我們確保自己不再使用這些資源了,要及時關閉,否則可能引起內存泄漏。因為有些操作不僅僅只是涉及到Dalvik虛擬機,還涉及到底層C/C++等的內存管理,不能完全寄希望虛擬機幫我們完成內存管理。
在這些資源不使用的時候,記得調用相應的類似close()、destroy()、recycler()、release()等函數,這些函數往往會通過jni調用底層C/C++的相應函數,完成相關的內存釋放。
七、集合對象沒有及時清理引起的內存泄漏
我們通常會把一些對象裝入到集合中,當不使用的時候一定要記得及時清理集合,讓相關對象不再被引用。如果集合是static、不斷的往里面添加東西、又忘記去清理,肯定會引起內存泄漏。
使用 LeakCanary 檢測 Android 的內存泄漏
內存泄漏防不勝防,通過LeakCanary工具,我們能在開發測試階段發現絕大多數的內存泄漏。這個工具是開源的,使用也非常方便,而且能夠相對準確定位出是哪里出現內存泄漏。
下面以AndroidStudio為例介紹LeakCanary的使用:
1、在build.gradle中配置leakcanary的引用
compile 'com.squareup.leakcanary:leakcanary-android:1.4-beta2'
2、使用RefWatcher監測本該被回收的對象。
LeakCanary.install() 會返回一個預定義的 RefWatcher,同時也會啟用一個 ActivityRefWatcher,用于自動監控調用 Activity.onDestroy() 之后泄露的 activity。
在自己的Application中添加如下代碼:

只需要上述兩個步驟之后,LeakCanary就會自動監測內存泄漏,如果有內存泄漏在手機上面會提示,通知欄也會有通知,點擊進去之后可以看到具體內存泄露的地方。debug的話,在控制臺也會有相關的log輸出。
總結:
1、Handler持有的引用最好使用弱引用,在Activity被釋放的時候要記得清空Message,取消Handler對象的Runnable;
2、非靜態內部類、非靜態匿名內部類會自動持有外部類的引用,為避免內存泄露,可以考慮把內部類聲明為靜態的;
3、對于生命周期比Activity長的對象,要避免直接引用Activity的context,可以考慮使用ApplicationContext
4、廣播接收器、EventBus等的使用過程中,注冊/反注冊應該成對使用;
5、不再使用的資源對象Cursor、File、Bitmap等要記住正確關閉;
6、集合里面的東西、有加入就應該對應有相應的刪除。

文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()