每日一道面試題(第1期)---自定義handler如何有效保證記憶體洩漏問題

leaps發表於2019-05-20

零零碎碎的東西總是記不長久,僅僅學習別人的文章也只是他人咀嚼後留下的殘渣。無意中發現了這個每日一道面試題,想了想如果只是簡單地去思考,那麼不僅會收效甚微,甚至難一點的題目自己可能都懶得去想,堅持不下來。所以不如把每一次的思考、理解以及別人的見解記錄下來。不僅加深自己的理解,更要激勵自己堅持下去。

handler作用

SDK文件是這麼說的。

There are two main uses for a Handler: (1) to schedule messages and runnables to be executed as some point in the future; and (2) to enqueue an action to be performed on a different thread than your own.

我們一般就是用來更新UI執行緒的。具體點就是在子執行緒進行耗時操作,比如獲取網路圖片,然後需要在主執行緒更新圖片,就需要handler+Message+Loop+MessageQueue來幫忙啦。

但是如果你直接建立一個handler物件,然後重寫內部handlerMessage方法,那麼AS一定會提醒你會有記憶體洩漏的可能。

Handler

為什麼會造成記憶體洩漏

Android記憶體洩漏:需要被GC回收的物件因為被其他存活的物件所持有引用,而導致GC不能回收此物件。那麼這塊記憶體就會在程式執行期間長期被佔據,造成系統記憶體的浪費,使系統執行緩慢甚至崩潰。

那麼handler什麼時候會造成記憶體洩漏呢?

傳送延遲訊息

眾所周知,匿名內部類持有外部類的引用,那麼handler物件就會持有activity物件的引用。handler傳送message到MessageQueue,message持有handler的引用,而MessageQueue會持有message的引用,而MessageQueue是屬於TLS(ThreadLocalStorage)執行緒,是與Activity不同的生命週期。

所以當Activity的生命週期結束後,而MessageQueue中還存在未處理的訊息,那麼上面一連串的引用關係就不允許Activity的物件被回收,就造成了記憶體洩漏。

解決辦法

知道了記憶體洩漏是由引用鏈造成的,那麼解決方法也就是破壞上面的引用鏈。

首先是引用的型別,有強引用、軟引用、弱引用、虛引用,上面的引用鏈都是強引用。

引用

所以第一種方法,自定義靜態內部類,如果想使用外部類的方法,那就通過弱引用的方法引入Activity物件。

public class BaseActivity extends Activity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    public void myHandleMessage(Message msg){}

    static class MyHandler extends Handler{
        WeakReference<BaseActivity> mActivityReference;
        public MyHandler(BaseActivity activity){
            mActivityReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            BaseActivity activity = mActivityReference.get();
            if(activity != null){
                activity.myHandleMessage(msg);
            }
        }
    }
}
複製程式碼

你可以自定義在BaseActivity中,在其他Activity中建立Myahndler物件,通過重寫myHandleMessage方法進行訊息處理。

這種方法就是處理了Activity與Handler之間的引用,這種引用可以再GC時被回收。

第二種,就是處理後面的引用。既然是Activity要被回收時還有未被處理的訊息,那麼在Activity要被回收時清除訊息就可以了。

@Override
    protected void onDestroy() {
        super.onDestroy();
        if(mHandler != null){
            mHandler.removeCallbacksAndMessages(null);
        }
    }

複製程式碼

相關文章