1. 程式人生 > >handler導致記憶體洩露的真正原因

handler導致記憶體洩露的真正原因

handler是我們在更新UI時經常使用到的類,但是不注意的話,很容易就導致記憶體洩露,最後導致OOM,故現在探究下handler導致記憶體洩露的原因及有哪些常用的解決辦法。

先看下面一段程式碼:

 可以看到這段程式碼編輯器為我們標出了黃色,並且提示如下:

This Handler class should be static or leaks might occur (anonymous android.os.Handler) less... (Ctrl+F1)
Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected. If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue. If the Handler is using the Looper or MessageQueue of the main thread, you need to fix your Handler declaration, as follows: Declare the Handler as a static class; In the outer class, instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler; Make all references to members of the outer class using the WeakReference object.

簡單翻譯如下:

由於handler定義為內部類,可能會阻止GC。如果handler的Looper或MessageQueue 非主執行緒,那麼沒有問題。如果handler的Looper或MessageQueue 在主執行緒,那麼需要按如下定義:定義handler為靜態內部類,當你例項化handler的時候,傳入一個外部類的弱引用,以便通過弱引用使用外部類的所有成員。

理解這段話很重要,為何要用static、為何要使用WeakReference 引用外部類,我自己理解如下:

①先說handler導致activity記憶體洩露的原因:

handler傳送的訊息在當前handler的訊息佇列中,如果此時activity finish掉了,那麼訊息佇列的訊息依舊會由handler進行處理,若此時handler宣告為內部類(非靜態內部類),我們知道內部類天然持有外部類的例項引用,那麼就會導致activity無法回收,進而導致activity洩露。

為何handler要定義為static?

因為靜態內部類不持有外部類的引用,所以使用靜態的handler不會導致activity的洩露

為何handler要定義為static的同時,還要用WeakReference 包裹外部類的物件?

這是因為我們需要使用外部類的成員,可以通過"activity. "獲取變數方法等,如果直接使用強引用,顯然會導致activity洩露。

其實,上面那段英文已經說的非常清楚了,下面用例子進行說明:

演示記憶體洩露

看下面程式碼:

private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);

        }
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handleroom);

        findViewById(R.id.btn_send).setOnClickListener(this);
        tv = findViewById(R.id.tv);

        // 延時5min傳送一個訊息,此時handler是持有activity引用的
        mHandler.sendEmptyMessageDelayed(1, 5 * 60 * 1000);
    }

聲明瞭一個匿名內部類的handler,在onCreat中延時兩分鐘傳送了一個訊息,將程式執行起來,

初始記憶體佔用:61M

 然後我們旋轉螢幕,螢幕旋轉幾次之後94M左右,每次旋轉都會導致記憶體佔用增加

注意這時候的操作:如果我們快速的進行螢幕旋轉,然後強制進行GC回收(在5min之內),那麼記憶體佔用是降不下來的,說明發生了記憶體洩露。這時候我們再等個幾分鐘,以保證handler處理完所有的訊息,會發現記憶體佔用降下來了,我的理解是幾分鐘不操作後,handler會處理完所有的訊息,這時handler會被回收,進而引用的activity也得到了回收,所以最後記憶體佔用和初始是差不多的。

使用靜態內部類的handler

程式碼如下:

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handleroom);

        findViewById(R.id.btn_send).setOnClickListener(this);
        tv = findViewById(R.id.tv);

        // 延時5min傳送一個訊息,此時handler是持有activity引用的
//        mHandler.sendEmptyMessageDelayed(1, 5 * 60 * 1000);
        handler.sendEmptyMessageDelayed(0, 5 * 60 * 1000);
    }

 /**
     * 避免handler記憶體洩露的寫法
     * handler為何會導致記憶體洩露:
     * 如果handler為內部類(非靜態內部類),那麼會持有外部類的例項,
     * 若在handler.sendMessage的時候,activity finish掉了,那麼此時activity將無法得到釋放
     * <p>
     * 如果申明handler為靜態內部類,則不會含有外部類的引用,
     * 但是需要在handler中更新UI(注意此時handler為static),則需要引入一個activity引用,
     * 顯然必須是弱引用,否則會導致和上面一樣的結果
     * <p>
     * 使用activity弱引用
     * 之所以使用弱引用,是因為handler為static,使用activity的弱引用來訪問activity物件內的成員
     */
    private static class MyHandler extends Handler {
        private WeakReference<HandlerOOMActivity> weakReference;
//        private HandlerOOMActivity activity;

        public MyHandler(HandlerOOMActivity activity) {
            weakReference = new WeakReference<>(activity);
//            this.activity = activity;
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Log.e(TAG, "handleMessage: ");
//            HandlerOOMActivity activity = weakReference.get();
            switch (msg.what) {
                case 0:
//                    if (activity != null) {
//                        activity.tv.setText("我是更改後的文字");
//                    }
//                    activity.tv.setText("我是更改後的文字");
                    break;
            }
        }
    }

 請忽略activity的弱引用,因為關係不太,之所以弱引用activity,是為了訪問activity物件的成員,看註釋掉的程式碼,如果強引用了activity,那麼將導致和上面一樣的結果!

初始記憶體佔用:60M

 如果我們快速的旋轉螢幕,那麼會發現剛開始記憶體佔用會增加,但是很快會降下來,如果強制GC,那麼會回到初始值,與我們預想的一樣。

使用單獨定義的handler類

這裡不再貼程式碼和演示了,通過上面的分析,同樣不會出現記憶體洩露,因為handler沒有持有activity的引用

既然handler導致的activity洩露是由於activity finish後持有activity引用的handler處理訊息導致的,那麼在activity finish之前移除所有的訊息,這時雖然內部類的handler仍然持有activity引用,但是由於handler是可以隨時被回收的,即持有activity引用的handler可以隨時被回收,有點類似於第一種情形,即讓所有的訊息處理完。這樣是不是也是可行的?答案是肯定的。

 可以看下上面的記憶體佔用圖,和前兩種情形不太一樣,是在達到了較高的閾值之後馬上降下來,而前兩種是緩慢增加緩慢下降的,這是因為這種handler依舊含有activity的引用,所以handler物件佔用的記憶體相對較高。

總結:

* 避免handle記憶體洩露的辦法
* 1.使用static 修飾的handler,但是一般會弱引用activity物件,因為要使用activity物件中的成員
* 2.單獨定義handler,同樣可以弱引用activity
* 3.使用內部類的handler,在onDestroy方法中removeCallbacksAndMessages

以上