1. 程式人生 > >安卓Handler當做內部類,導致記憶體洩露的問題

安卓Handler當做內部類,導致記憶體洩露的問題

this handler should be static or leaks might occur

 

How to Leak a Context: Handlers & Inner Classes

Context是怎麼洩露的:Handlers & Inner Classes

Consider the following code:

考慮下面的程式碼

public class SampleActivity extends Activity {

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

While not readily obvious, this code can cause cause a massive memory leak. Android Lint will give the following warning:
儘管不是那麼明顯,但這段程式碼會導致大量記憶體洩露。Android的Lint工具會給出如下警告:
In Android, Handler classes should be static or leaks might occur.
在Android中,Handler類應該被靜態修飾,否則可能會出現記憶體洩露。
But where exactly is the leak and how might it happen? Let's determine the source of the problem by first documenting what we know:
但是究竟是在哪裡洩露了記憶體,如何洩露的,下面讓我們根據已有的知識來分析下問題的原因:


When an Android application first starts, the framework creates a Looper object for the application's main thread. A Looper implements a simple message queue, processing Message objects in a loop one after another. All major application framework events (such as Activity lifecycle method calls, button clicks, etc.) are contained inside Message objects, which are added to the Looper's message queue and are processed one-by-one. The main thread's Looper exists throughout the application's lifecycle.
當Android應用首次啟動時,framework會在應用的UI執行緒建立一個Looper物件。Looper實現了一個簡單的訊息佇列並且一個接一個的處理佇列中的訊息。應用的所有事件(比如Activity生命週期回撥方法,按鈕點選等等)都會被當做一個訊息物件放入到Looper的訊息佇列中,然後再被逐一執行。UI執行緒的Looper存在於整個應用的生命週期內。

When a Handler is instantiated on the main thread, it is associated with the Looper's message queue. Messages posted to the message queue will hold a reference to the Handler so that the framework can call Handler#handleMessage(Message) when the Looper eventually processes the message.
當在UI執行緒中建立Handler物件時,它就會和UI執行緒中Looper的訊息佇列進行關聯。傳送到這個訊息佇列中的訊息會持有這個Handler的引用,這樣當Looper最終處理這個訊息的時候framework就會呼叫Handler#handleMessage(Message)方法來處理具體的邏輯。
In Java, non-static inner and anonymous classes hold an implicit reference to their outer class. Static inner classes, on the other hand, do not.
在Java中,非靜態的內部類或者匿名類會隱式的持有其外部類的引用,而靜態的內部類則不會。
So where exactly is the memory leak? It's very subtle, but consider the following code as an example:
那麼,記憶體到底是在哪裡洩露的呢?其實洩漏發生的還是比較隱晦的,但是再看看下面這段程式碼:

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();
  }
}

When the activity is finished, the delayed message will continue to live in the main thread's message queue for 10 minutes before it is processed. The message holds a reference to the activity's Handler, and the Handler holds an implicit reference to its outer class (the SampleActivity, in this case). This reference will persist until the message is processed, thus preventing the activity context from being garbage collected and leaking all of the application's resources. Note that the same is true with the anonymous Runnable class on line 15. Non-static instances of anonymous classes hold an implicit reference to their outer class, so the context will be leaked.
當activity被finish的時候,延遲傳送的訊息仍然會存活在UI執行緒的訊息佇列中,直到10分鐘後它被處理掉。這個訊息持有activity的Handler的引用,Handler又隱式的持有它的外部類(這裡就是SampleActivity)的引用。這個引用會一直存在直到這個訊息被處理,所以垃圾回收機制就沒法回收這個activity,記憶體洩露就發生了。需要注意的是:15行的匿名Runnable子類也會導致記憶體洩露。非靜態的匿名類會隱式的持有外部類的引用,所以context會被洩露掉
To fix the problem, subclass the Handler in a new file or use a static inner class instead. Static inner classes do not hold an implicit reference to their outer class, so the activity will not be leaked. If you need to invoke the outer activity's methods from within the Handler, have the Handler hold a WeakReference to the activity so you don't accidentally leak a context. To fix the memory leak that occurs when we instantiate the anonymous Runnable class, we make the variable a static field of the class (since static instances of anonymous classes do not hold an implicit reference to their outer class):

解決這個問題也很簡單:在新的類檔案中實現Handler的子類或者使用static修飾內部類。靜態的內部類不會持有外部類的引用,所以activity不會被洩露。如果你要在Handler內呼叫外部activity類的方法的話,可以讓Handler持有外部activity類的弱引用,這樣也不會有洩露activity的風險。關於匿名類造成的洩露問題,我們可以用static修飾這個匿名類物件解決這個問題,因為靜態的匿名類也不會持有它外部類的引用。

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();
  }
}

The difference between static and non-static inner classes is subtle, but is something every Android developer should understand. What's the bottom line? Avoid using non-static inner classes in an activity if instances of the inner class could outlive the activity's lifecycle. Instead, prefer static inner classes and hold a weak reference to the activity inside.
靜態和非靜態內部類的區別是非常微妙的,但這個區別是每個Android開發者應該清楚的。那麼底線是什麼?如果要例項化一個超出activity生命週期的內部類物件,避免使用非靜態的內部類。建議使用靜態內部類並且在內部類中持有外部類的弱引用。