關於Android中的內存泄漏問題,你需要了解這些

一、Android中內存泄露的經典場景

1. Activity中的Handler長期持有activity引用導致activity泄漏。

原因:

(1). 在Activity內new一個Handler時,該handler實例會對Activity持有一個隱式的引用。
(2). Looper通過loop方法不斷循環消息隊列,而消息隊列中的Message又會持有handler的引用。
(3). 當Activity被finish掉後,消息隊列未處理完,而handler持有對activity引用,因此即使activity被finish掉,也不會被gc掉。

解決方法:

(1) 將內部類handler創建到外部類,handler僅持有activity的弱引用。
(2) 將內部類handler設爲靜態,因爲靜態內部類不持有對外部類的引用。
(3) 或者在activity的onDestory方法中幹掉handler的所有callback和message,mHandler.removeCallbacksAndMessages(null);
2. 非靜態匿名內部類、匿名內部類造成內存泄露

原因:

(1)當我們在Activity中直接new一個Thread,但當我們的Activity被finish時,Thread仍在運行,就造成了內存泄露

解決方式:

(1)同樣把Thread定義爲靜態的內部類,這樣就不會持有外部類的引用。
3. 單例+依賴注入

原因:

(1)當我們在單例類中(靜態實體,生命週期和App一樣,非常長)持有Activity/context的對象引用,造成內存泄露

解決方法:

(1)單例的類儘量不要持有Activity/context對象,非要持有的話,可以選擇ApplicationContext。
(2)可以持有Activity/context的弱引用,在它們被內存回收的時候避免強引用而造成無法回收的問題。
4. 資源對象沒關閉造成的內存泄漏

原因:

(1)Cursor、InputStream、OutputStream、Socket等在使用後,沒有進行關閉,然後長期堆積就會導致內存泄漏.

解決方案:

(1)在使用完畢後進行關閉。例如使用完cursor,可以在finally進行cursor.close()。其他同理。

5. webview導致的內存泄漏

原因:

(1) 在銷燬webview前沒有把webview從父view中移除,會導致內存泄漏。

解決:

(1) 在銷燬webview前一定要onDetachedFromWindow,我們先將webview從它的父view中移除再調用destroy方法。

    @Override
    public void destroy() {
        try {
            ViewParent parent = getParent();
            if (parent != null) {
                ((ViewGroup) parent).removeView(this);
            }
            clearView();
            removeAllViews();
            super.destroy();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

二、提前發現

1.通過工具

(1)通過Android Studio的Monitor工具,打開Memory標籤查看內存變化情況,通過dump java head採集數據,生成hprof文件
(2)通過MAT工具,導入hprof文件,它會幫我們分析內存泄露的原因
(3)通過LeakCanary插件,LeakCanary會在app運行時分析當前的內存快照,找到對象的引用鏈,並顯示到頁面上,好處是可以直接在手機上查看(如果檢測到泄漏會發送到通知欄,點擊通知欄就可以跳轉到具體的泄漏分析頁面。)
2.通過代碼

(1)debug版本可以起一個長期工作的線程LeakThread在後臺專門做泄漏檢測
(2)向Application註冊一個頁面生命週期的監聽:application.registerActivityLifecycleCallbacks
(3)在監聽類中對 onActivityDestoryed(Activity activity) 的事件回調做處理:
         如果一個Activity走到onDestroy,那麼這個Activity對象就是需要被回收的目標。
         我們聲明一個檢測對象的弱引用ref = new WeakReference(activity)
(4)在LeakThread中我們每隔一段時間檢測一下ref.get() 是否爲空,爲空說明activity已被釋放。不爲空可以手動觸一次發gc;如果超過一段時間,比如50s,頁面對象還未被清理,我們可以推斷內存泄漏的發生。
(5)當內存泄漏發生時,提示給開發者,並自動dump出.prof文件。

三、常見的內存泄漏檢測工具

1.MAT

MAT出自於Eclipse公司,可以利用Android Studio的Profile工具裏面的內存工具,導出.hprof文件,然後通過Android SDK的hprof-conv命令把.hprof文件轉爲MAT工具支持的標準.hrpof格式文件,再用MAT打開標準格式的.hrpof文件進行分析,通過分析懷疑對象是否存在,以及排除軟、弱、虛引用後查看其引用路徑,從而確定泄漏途徑。

hprof-conv命令如下:

// mac系統切換到~/Library/Android/sdk/tools/目錄執行以下命令
hprof-conv android.hprof mat.hprof

⚠️MacOS打開MAT會報錯,正確打開姿勢是右鍵選擇“顯示包內容”,命令行進入MacOS目錄,執行命令打開:

./MemoryAnalyzer -data ./workspace

2.LeakCanary

LeakCanary是Square公司開源的一個內存泄漏檢測工具。

  • 官方文檔:https://square.github.io/leakcanary/
  • 使用方法:《性能優化工具 - LeakCanary》
  • 觸發檢測時機:每當Activity/Fragment執行完onDestory的時候,會觸發LeakCanary初始化RefWatcher檢測該Activity/Fragment是否還在內存中,從而判斷是否內存泄漏。
  • 觸發dump時機:當Activity/Fragment被銷燬時,ObjectWatcher會對它們持有弱引用,並且會進行gc,gc完畢5s鍾後查看Activity/Fragment是否還存在,如果存在的話,就會認爲存在潛在的內存泄漏。LeakCanary並不會在發現潛在內存泄漏時就進行dump內存堆棧,而是等到泄漏對象超過一定閾值(APP在前臺的話默認5個,如果APP在後臺的話默認1個)纔會進行dump內存堆棧。
  • 如何分析內存堆棧:LeakCanary會使用Shark工具進行內存分析,對於每一個泄漏的對象,它都會找到一個阻止泄漏對象被gc的路徑,這個路徑就是從垃圾回收器裏面找到的泄漏對象的強引用路徑。

四、幫助定位線上OOM的方法

1.通過lifecycler監聽每一個Activity的創建和銷燬

對於一些顯式的OOM,如針對一些顆粒度是Activity的泄露,可以在每個Activity的創建和銷燬打印Xlog日誌,在進行定位線上OOM的時候,可以通過撈取用戶日誌,過濾相關日誌,最終定位是哪一個Activity銷燬的,從而解決一些顆粒度爲Activity的內存泄露,如退出Activity沒有移除延時任務等。

利用這種方法,除了發現一些比較明顯的OOM之外,還有作爲線索定位其他問題。之前通過這個日誌定位到一個由於加載大圖導致撐爆內存的問題,當時查了很多用戶的日誌都沒有線索,直到後來撈取到一個用戶的日誌從啓動到OOM崩潰只花了兩分鐘,於是就跟着用戶的操作,進到某一個用戶的個人頁,發現本地也能復現問題,這才定位出來是由於該用戶的個人頁裏面比較多九宮格圖片,在一些比較差的設備由於系統分給App的內存比較小,所以就很容易造成OOM,最終得以解決問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章