Android 關於Handler內存泄漏的那些事

在上一篇文章《Android Handler機制完全解析》中,我們從源碼的角度分析了Hanlder機制,接下來繼續學習Handler,本篇文章主要講解的是Handler可能會導致的內存泄漏以及解決方案。

1.爲什麼會發生內存泄漏

在平時使用Handler的時候,我們通常會這樣定義:

// 定義一個Handler對象,並實現handleMessage方法
Handler handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        // 在此接收子線程發送的消息
    }
};

嗯,看起來沒有什麼問題,但是Android Lint卻給出了警告:

This Handler class should be static or leaks might occur

意思是說這個Handler應該定義成靜態的,否則可能會內存溢出。什麼鬼,怎麼還會內存溢出,於是百…額…不對…Google一下,發現Google的工程師Romain Guy已經在論壇中做出過解釋:

I wrote that debugging code because of a couple of memory leaks I found in the Android codebase. Like you said, a Message has a reference to the Handler which, when it’s inner and non-static, has a reference to the outer this (an Activity for instance.) If the Message lives in the queue for a long time, which happens fairly easily when posting a delayed message for instance, you keep a reference to the Activity and “leak” all the views and resources. It gets even worse when you obtain a Message and don’t post it right away but keep it somewhere (for instance in a static structure) for later use.

學好一門外語是多麼的重要,翻譯翻譯:

我在Android代碼庫中發現了一些內存泄漏,爲此我寫了調試代碼進行測試,像你說的一樣,Message會持有一個對Handler的引用,當這個Handler是非靜態內部類的時候,又會持有一個對外部類的引用(比如Activity)。如果發送一條延時的Message,由於這個Message會長期存在於隊列中,就會導致Handler長期持有對Activity的引用,從而引起視圖和資源泄漏。當你發送一條延時的Mesaage,並且把這個Message保存在某些地方(例如靜態結構中)備用,內存泄漏會變得更加嚴重。

先說下什麼是內存泄漏:

內存泄漏(Memory Leak)是指程序中己動態分配的堆內存由於某種原因程序未釋放或無法釋放,造成系統內存的浪費,導致程序運行速度減慢甚至系統崩潰等嚴重後果。

寫段代碼說明一下:

public class HandlerActivity extends BaseActivity {

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

        handler.sendEmptyMessageDelayed(0, 10 * 60 * 1000);
        finish();
    }

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

使用Handler發送一條延時消息,然後關閉Activity,在Activity被關閉之後,Message將在消息隊列中存在10分鐘才能被執行到,這個Message持有一個對Handler的引用,Handler又持有一個對當前Activity的引用,這些引用會在Message被執行之前一直保持,這樣當前Activity就不會被垃圾回收機制回收,從而導致內存泄漏。

2.如何解決

那麼,該如何解決這個問題呢,Romain Guy給出了他的建議寫法:

class OuterClass {

  class InnerClass {

    private final WeakReference<OuterClass> mTarget;

    InnerClass(OuterClass target) {
           mTarget = new WeakReference<OuterClass>(target);
    }

    void doSomething() {
           OuterClass target = mTarget.get();
           if (target != null) {
                target.do();    
           }
     }
}

在內部類的構造方法中,創建一個對外部類的弱引用,然後再內部類的方法中通過弱引用獲取外部類對象,進行非空判斷後再進行操作,OK,修改一下我們的代碼:

public class HandlerActivity extends BaseActivity {

    @Bind(R.id.tv_handler)
    TextView tvHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        ButterKnife.bind(this);

        new WeakHandler(this).sendEmptyMessageDelayed(0, 10 * 60 * 1000);
        finish();
    }

    private static class WeakHandler extends Handler {

        WeakReference<HandlerActivity> weakReference;

        public WeakHandler(HandlerActivity activity) {
            weakReference = new WeakReference<HandlerActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            HandlerActivity activity = weakReference.get();
            if (activity != null && activity.tvHandler != null) {
                activity.tvHandler.setText("收到Handler發送的消息");
            }
        }
    }
}

因爲靜態內部類不會持有對外部類的引用,所以定義一個靜態的Handler,這樣Acitivity就不會被泄漏了,同時讓Handler持有一個對Activity的弱引用,這樣就可以happy的在Handler中調用Activity中的資源或者方法了。

如果在關閉Activity之後,不需要對消息隊列中的消息進行處理了,可以在onDestory方法中加入下面這段代碼:

// 移除所有消息
handler.removeCallbacksAndMessages(null);

// 移除單條消息
handler.removeMessages(what);

嗯,Handler可能導致的內存泄漏以及解決方案到這裏就講完了。

3.寫在最後

歡迎同學們吐槽評論,如果你覺得本篇博客對你有用,那麼就留個言或者頂一下吧(^-^)

發佈了64 篇原創文章 · 獲贊 275 · 訪問量 27萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章