1. 程式人生 > >Handler記憶體洩漏分析與解決方法

Handler記憶體洩漏分析與解決方法

最近整理完Android中訊息機制的知識後,想到Handler記憶體洩漏相關的問題也可以順便整理一下,便有了這篇文章,也方便以後自己查閱

為什麼Handler會造成記憶體洩漏

下面是一段簡單的Handler使用

public class MainActivity extends AppCompatActivity {

    private Handler mHandler = new Handler(){
        @Override
        public boolean handleMessage(Message msg) {

            return false;
        }
    };

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

        new Thread() {
            @Override
            public void run() {
                Message msg = new Message();
                msg.obj = "Hello UI";
                mHandler.sendMessageDelayed(msg, 600000);
            }

        }.start();
    }
}

在啟動應用程式時,Looper就已經會在主執行緒序被建立了,Looper會建立一個訊息佇列MessageQueue來存放訊息,應用程式中訊息都會發送到這個訊息佇列中,並且會伴隨程式的整個生命週期。當我們像上面那樣建立一個Handler物件之後,Handler就會與主執行緒中的Looper繫結起來,當我們在子執行緒中傳送Message時,Handler會把自身封裝在Message中,所以Message就持有了這個Handler的引用。

而在Java中,非靜態內部類會隱式的持有一個外部類物件的引用,當使用內部類(匿名類)來建立Handler的時候,Handler會隱式的持有一個外部類物件(Activity)的引用。通常我們非同步處理任務之後需要更新UI,如果該任務還未完成時,使用者就finish()掉這個Activity。正常情況下該Activity不再使用了,則GC(垃圾回收機制)檢查的時候會把它回收掉。但是由於子執行緒中的任務尚未完成,子執行緒中的Message會持有一個Handler的引用,而Handler又持有這Activity的引用,聯絡如下:
引用圖


所以這就導致Activity無法被回收,引發記憶體洩漏的問題

怎麼解決

在Android Studio中使用上面的方式建立Handler的時候會有警告

This Handler class should be static or leaks might occur

由提示資訊可知,只需要把Handler宣告成靜態內部類就行了,靜態內部類不持有外部類物件的引用,Activity就不會因為Handler的原因而無法被回收了

private static class MyHandler extends Handler{

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    }

上面建立Handler的方式看似已經把問題解決了,但是Handler不再持有Activity的引用了,所以導致程式不允許在Handler中操作Activity中的物件了,這時就需要在Handler中增加一個對Activity的弱引用(WeakReference)

    private static class MyHandler extends Handler{

        WeakReference<MainActivity> mainActivity;

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

        @Override
        public void handleMessage(Message msg) {
            if (mainActivity.get() != null){
                
            }
        }
    }

下面補充一點引用的知識:

引用型別 說明
強引用 當記憶體空 間不足,Java虛擬機器寧願丟擲OutOfMemoryError錯誤,使程式異常終止,也不會靠隨意回收具有強引用的物件來解決記憶體不足問題
軟引用 (SoftReference) 在記憶體不足時,GC會回收軟引用指向的物件
弱引用(WeakReference) 不管記憶體足不足,GC都可能回收弱引用指向的物件
虛引用(PhantomReference ) 如果一個物件僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收

除了通過弱應用的方式來解決Handler記憶體洩漏的問題之外,還有一種解決辦法,就是在Activity關閉的時候使用相對應的removeCallbacks()方法來把訊息物件從訊息佇列中刪除,例如:

public class MainActivity extends AppCompatActivity {

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

            }
    };

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

    @Override
    protected void onDestroy() {
        super.onDestroy();
        myHandler.removeCallbacksAndMessages(null);
    }
}

總結

記憶體洩露在 Android 開發中是一個比較嚴重的問題,系統給每一個應用分配的記憶體是固定的,一旦發生了記憶體洩露,就會導致該應用可用記憶體越來越小,嚴重時會發生 OOM 導致 Force Close,所以在平時的開發中應該避免引發記憶體洩漏