性能優化--handler內存泄漏分析

開發中,如果代碼不規範很容易產生內存泄漏,比如Handler、Context、線程等使用。本文使用Android Studio自帶的Profiler和MAT工具進行內存泄漏分析。

一、內存泄漏

1、定義handler
private Handler mHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(@NonNull Message msg) {
            mHandler.sendEmptyMessage(0);
            return false;
        }
    });
2、在onCreate()方法調用
mHandler.sendEmptyMessage(0);

以上件簡單大的幾句代碼就發生了內存泄漏,可以用profiler工具查看。

二、profiler

1、使用profiler運行程序

在這裏插入圖片描述
點擊小圖標即可

2、選擇分析Memory

在這裏插入圖片描述
點擊MEMORY一欄

3、按返回鍵退出當前頁面,然後點擊垃圾桶小圖標

在這裏插入圖片描述

4、在回收後截取內存的一塊

在這裏插入圖片描述

5、底部按包名查找,找到對應包名下的文件

在這裏插入圖片描述
Allocations:申請的大小
Deallocations:釋放的大小
Total Count:對象引用次數
Shallow Size:對象自身佔用的內存大小

這裏重點關注第三個參數,我們已經退出了當前的MainActivity界面,但是發下對象的引用個數爲1,說明它沒有被回收。發生了內存泄漏。下面使用MAT工具,分析具體的內存泄漏原因。

三、MAT

1、點擊如下圖標:

在這裏插入圖片描述

2、將選中的內存保存下來,後綴爲hprof

在這裏插入圖片描述

3、只分析內存部分,需要使用命令行對文件進行過濾

找到hprof-conv.exe所在的目錄,一般在SDK目錄下的platform-tools目錄。截圖如下:
在這裏插入圖片描述
把文件複製到這裏。然後執行命令行,生成新的文件memory-2.hprof。命令行如下:
在這裏插入圖片描述

hprof-conv -z 當前文件 目標文件
4、用MAT工具打開當前文件

在這裏插入圖片描述
(1)點擊Histogram。
在這裏插入圖片描述
(2)在這裏可以輸入你要分析的文件名稱,由上面的Profile工具分析可以看出泄漏的文件爲MainActivity。所以直接輸入MainActivity,按回車,右擊當前文件
在這裏插入圖片描述
排除掉所有的弱引用和軟引用等。我們只分析強引用對象。
(3)展開引用,排除系統的引用。查看哪些外部對象引用它
在這裏插入圖片描述
(4)如下圖
在這裏插入圖片描述
排除系統的,發現MainActiivty$2內部類仍然連接着原始對象this。說明this是一個根對象,Handler是與他相連的。導致它無法回收。

四、解決

(1)方式一:使用移除消息的方式
在onDestroy()方法中添加如下代碼:

mHandler.removeMessages(0);

優點:能夠解決內存泄漏。
缺點:如果我們需要在界面結束的時候,需要繼續在後臺完成一個操作。完成後需要Handler通知,那麼這種方式就失效了。

(2)方式二:使用弱引用
代碼如下:

private class MyHandler extends Handler {
        private WeakReference<MainActivity> mWeakReference;

        MyHandler(MainActivity activity) {
            mWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            Log.e("11111111111", "2222222222222");
            if (mWeakReference.get() != null) {

                mHandler.sendEmptyMessage(0);
            }
        }
    }

    private Handler mHandler = new MyHandler(this);

優點:能夠解決內存泄漏。
缺點:由於採用弱引用,不清楚什麼時候被回收。代碼不可控。

(3)方式三:使用靜態內部類

代碼如下:

private static class MyHandler extends Handler{
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            Log.e("11111111111","2222222222222");
            mHandler.sendEmptyMessage(0);
        }
    }

private  static Handler mHandler = new MyHandler();

增加了static修飾符。靜態內部類不持有外部類的引用。

優點:能夠解決內存泄漏。
缺點:由於static修飾的對象,生命週期比較長。所以大量使用static容易導致內存溢出。

綜上,還是要看自己的具體業務邏輯,採用不同的方式。

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