[轉]How to Leak a Context: Handlers & Inner Classes

來源:互聯網
上載者:User

標籤:android   style   blog   http   io   ar   color   os   使用   

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:

In Android, Handler classes should be static or leaks might occur.

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:

  1. 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, processingMessage objects in a loop one after another. All major application framework events (such as Activity lifecycle method calls, button clicks, etc.) are contained inside Messageobjects, 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.

  2. 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 theHandler so that the framework can call Handler#handleMessage(Message) when the Looper eventually processes the message.

  3. 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.

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() { }    }, 60 * 10 * 1000);     // 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 (theSampleActivity, 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.

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):

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, 60 * 10 * 1000);        // 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 outlive the activity‘s lifecycle. Instead, prefer static inner classes and hold a weak reference to the activity inside.

As always, leave a comment if you have any questions and don‘t forget to +1 this blog in the top right corner! :)

 

【譯】什麼導致了Context泄露:Handler&內部類

思考下面代碼

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

如果沒有仔細觀察,上面的代碼可能導致嚴重的記憶體泄露。Android Lint會給出下面的警告:

In Android, Handler classes should be static or leaks might occur.

但是到底是泄漏,如何發生的?讓我們確定問題的根源,先寫下我們所知道的
1、當一個Android應用程式第一次啟動時,Android架構為應用程式的主線程建立一個Looper對象。一個Looper實現了一個簡單的訊息佇列,在一個迴圈中處理Message對象。所有主要的應用程式架構事件(如活動生命週期方法調用,單擊按鈕,等等)都包含在Message對象,它被添加到Looper的訊息佇列然後一個個被處理。主線程的Looper在應用程式的整個生命週期中存在。
2、當一個Handle在主線程被執行個體化,它就被關聯到Looper的訊息佇列。被發送到訊息佇列的訊息會持有一個Handler的引用,以便Android架構可以在Looper最終處理這個訊息的時候,調用Handler#handleMessage(Message)。
3、在Java中,非靜態內部類和匿名類會隱式地持有一個他們外部類的引用。靜態內部類則不會。

那麼,到底是記憶體流失?好像很難懂,讓我們以下面的代碼作為一個例子

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

當這個Activity被finished後,延時發送的訊息會繼續在主線程的訊息佇列中存活10分鐘,直到他們被處理。這個訊息持有這個Activity的Handler引用,這個Handler有隱式地持有他的外部類(在這個例子中是SampleActivity)。直到訊息被處理前,這個引用都不會被釋放。因此Activity不會被記憶體回收機制回收,泄露他所持有的應用程式資源。注意,第15行的匿名Runnable類也一樣。匿名類的非靜態執行個體持有一個隱式的外部類引用,因此context將被泄露。

為瞭解決這個問題,Handler的子類應該定義在一個新檔案中或使用靜態內部類。靜態內部類不會隱式持有外部類的引用。所以不會導致它的Activity泄露。如果你需要在Handle內部調用外部Activity的方法,那麼讓Handler持有一個Activity的弱引用(WeakReference)以便你不會意外導致context泄露。為瞭解決我們執行個體化匿名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) {                // ...            }        }    }}

靜態和非靜態內部類的區別是比較難懂的,但每一個Android開發人員都應該瞭解。開發中不能碰的雷區是什嗎?不在一個Activity中使用非靜態內部類, 以防它的生命週期比Activity長。相反,盡量使用持有Activity弱引用的靜態內部類。

 

[轉]How to Leak a Context: Handlers & Inner Classes

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.