[Android] The handler class should be static or leaks might occur原因及解決方法


翻譯自http://www.androiddesignpatterns.com/2013/01/inner-class-handler-memory-leak.html

 

在主線程中使用Handler對象,比如下面的代碼

public class SampleActivity extends Activity {

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

這段代碼會產生隱秘的內存溢出的錯誤,Android Lint會給出羡慕的警告:

The handler class should be static or leaks might occur.

但是究竟哪一部分內存會在什么情況下溢出呢?

要解決這一問題需要了解一下安卓系統背景知識:

1. 當一個Android應用啟動的時候,Android系統為這個應用的主線程創建一個Looper對象。Looper對象實現了簡單的消息隊列(message queue),依次處理循環(Looper)中的消息。所有的主要應用事件(比如Activity的生命周期,按鈕的點擊等)都被包裹為一個消息(Message)實體,被添加到Looper的消息隊列中依次處理。主線程的Looper在應用的整個生命周期中都存在。

2.當一個Handler對象在主線程中初始化時,它被關聯到Looper的消息隊列中。在Handler的sendMessage()方法被調用的時候,一個Message對象會被發往Looper的消息隊列中,被發送到消息隊列中的Message將會持有Handler的引用,然后系統才能在Looper處理Message時調用Handler對象的handleMessage(Message)方法。

3.在Java中,非靜態(non-static)內部和匿名類將會持有外部類的引用。相反,靜態的內部類不會持有外部類的引用。

更多Looper,Handler相關的知識可以在文后鏈接的博客中找到。

了解了這些背景之后我們來看一下內存在什么情況下溢出,首先看下面一段代碼:

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);
 
    // Post a message and delay its execution for 10 minutes.
    mLeakyHandler.postDelayed(new Runnable() {
      @Override
      public void run() { /* ... */ }
    }, 1000 * 60 * 10);
 
    // Go back to the previous Activity.
    finish();
  }
}

當Activity結束的時候,被延遲的Message會繼續存活在主線程中10分鍾,直到它被處理了。這個Message持有Activity的Handler對象的引用,同時Handler是Activity的非靜態(non-static)匿名內部類,所以Handler持有外部類也就是Activity的引用。這樣的引用關系會阻止Activity被Java GC回收,釋放系統持有的資源,直到Message被處理了。另外楊的引用對於匿名Runnable()對象也存在。

解決這個問題的方法可以有:在一個新的文件中繼承Handler類,或者使用一個靜態內部類。靜態內部類不會持有外部類的引用,所以Activity不會被泄露。如果需要在Handler中調用外部類(此處為Activiity)的方法,可以使Handler擁有Activity的弱引用(WeakReference),這樣就不會意外地泄露Activity。

為了解決實例化內部匿名Runnable類時導致的內存泄漏的問題,我們可以使內部匿名Runnable類變為外部類的靜態對象。

public class SampleActivity extends Activity {

  /**
   * Instances of static inner classes do not hold an implicit
   * reference to their outer class.
   */
  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) {
        // ...
      }
    }
  }

  private final MyHandler mHandler = new MyHandler(this);

  /**
   * Instances of anonymous classes do not hold an implicit
   * reference to their outer class when they are "static".
   */
  private static final Runnable sRunnable = new Runnable() {
      @Override
      public void run() { /* ... */ }
  };

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

    // Post a message and delay its execution for 10 minutes.
    mHandler.postDelayed(sRunnable, 1000 * 60 * 10);
    
    // Go back to the previous Activity.
    finish();
  }
}

 

在Activity中實例化內部類時,如果內部類可以在Activity的生命周期之外繼續存活於哦,那么這樣的內部類不能為非靜態的。這種情況應該使用靜態內部來並且持有外部類對象的弱引用。

 

 

相關鏈接:

http://www.cnblogs.com/codingmyworld/archive/2011/09/12/2174255.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM