Android 非靜態內部類導致的內存泄露(非static內部類)

我經常這麼聲明一個Hanlder

public class SampleActivity extends Activity {

  private final Handler mLeakyHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      // ...
    }
  }

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

    // 延時10分鐘發送一個消息
    mLeakyHandler.postDelayed(new Runnable() {
      @Override
      public void run() { }
    }, 60 * 10 * 1000);

    // 返回前一個Activity
    finish();
  }
}

上面這段代碼會導致內存泄露,它如何發生的?讓我們確定問題的根源,先寫下我們所知道的.
1、當一個Android應用程序第一次啓動時,Android框架爲應用程序的主線程創建一個Looper對象。一個Looper實現了一個簡單的消息隊列,在一個循環中處理Message對象。所有主要的應用程序框架事件(如Activity生命週期方法的調用,單擊按鈕,等等)都包含在Message對象中,它被添加到Looper的消息隊列然後一個個被處理。主線程的Looper在應用程序的整個生命週期中都存在。
2、當一個Handler在主線程中被實例化,它就被關聯到Looper的消息隊列。每個被髮送到消息隊列的消息會持有一個Handler的引用,以便Android框架可以在Looper最終處理這個消息的時候,調用這個Message對應的Handler的handleMessage(Message)。


public final class Message implements Parcelable {
    ...
    Handler target;
    ...
}

3、在Java中,非靜態的內部類和匿名類會隱式地持有一個他們外部類的引用,靜態內部類則不會。
4、通過handler發送的runnable對象,會被進一步包裝爲message對象,放入消息隊列.

所以, 對上面的例子來說, 當這個Activity被finished後,延時發送的消息會繼續在主線程的消息隊列中存活10分鐘,直到他們被處理。這個message持有handler對象,這個handler對象又隱式持有着SampleActivity對象.直到消息被處理前,這個handler對象都不會被釋放, 因此SampleActivity也不會被釋放。注意,這個匿名Runnable類對象也一樣。匿名類的非靜態實例持有一個隱式的外部類引用,因此SampleActivity將被泄露。

爲了解決這個問題,Handler的子類應該定義在一個新文件中或使用靜態內部類。靜態內部類不會隱式持有外部類的引用。所以不會導致它的Activity泄露。如果你需要在Handler內部調用外部Activity的方法,那麼讓Handler持有一個Activity的弱引用(WeakReference)是正確的解決方案。爲了解決我們實例化匿名Runnable類可能導致的內存泄露,我們將用一個靜態變量來引用他(因爲匿名類的靜態實例不會隱式持有它的外部類的引用)。


public class SampleActivity extends Activity {
    /**
    * 匿名類的靜態實例不會隱式持有他們外部類的引用
    */
    private static final Runnable sRunnable = new Runnable() {
            @Override
            public void run() {
            }
        };

    private final MyHandler mHandler = new MyHandler(this);

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

        // 延時10分鐘發送一個消息.
        mHandler.postDelayed(sRunnable, 60 * 10 * 1000);

        // 返回前一個Activity
        finish();
    }

    /**
    * 靜態內部類的實例不會隱式持有他們外部類的引用。
    */
    private static class MyHandler extends Handler {
        private final WeakReference<SampleActivity> mActivity;

        public MyHandler(SampleActivity activity) {
            mActivity = new WeakReference<SampleActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            SampleActivity activity = mActivity.get();

            if (activity != null) {
                // ...
            }
        }
    }
}




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