Handler Memory Leakage Analysis and Solution

Posted by loveitandhateit on Fri, 28 Jun 2019 23:34:45 +0200

I. Introduction

First, browse the handler code below:

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

This is a very common piece of code when using handler. However, it can cause serious memory leaks. In practice, we often get the following warnings:

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

II. Analysis

1. Android Perspective

When the Android application starts, the framework creates a Looper object for the main thread of the application. This Looper object contains a simple message queue Message Queue and can process messages in the queue in a loop. These messages include most application framework events, such as Activity lifecycle method calls, button clicks, etc., which are added to the message queue and processed one by one.

In addition, the Loper object of the main thread will accompany the entire life cycle of the application.

Then, when a Handler object is instantiated in the main thread, it automatically associates with the message queue of the main thread Looper. All message messages sent to the message queue have a reference to Handler, so when Looper processes the message, it calls back the [Handler#handleMessage(Message)] method to process the message.

2. Java Perspective

In java, both non-static internal classes and anonymous classes potentially refer to the external classes to which they belong. However, static inner classes do not.

3. Sources of leakage

Please browse the following code:

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 finishes, the delayed messages are stored in the message queue of the main thread for 10 minutes before being processed. Furthermore, as you can see from the above, this message holds a reference to handler, which in turn holds a potential reference to its external class (here, SampleActivity). This reference relationship is maintained until the message is processed, which prevents SampleActivity from being recycled by the garbage collector and leaks the application.

<<<<<<<HEAD Note that the Runnable class in the above code -- a non-static anonymous class -- also holds references to its external classes. This also leads to leakage.

======= Note that the Runnable class in the code above -- a non-static anonymous class -- also holds references to its external classes. This also leads to leakage.

IV. Leakage Solutions

First, the source of memory leaks has been identified above:

As long as there are unprocessed messages, the message will refer to handler, and the non-static handler will refer to the external class, namely Activity, resulting in the activity can not be recycled, resulting in leakage;

Runnable classes belong to non-static anonymous classes, which also refer to external classes.

To solve the problem, we need to make it clear that static internal classes do not hold references to external classes. So, we can put handler classes in separate class files, or use static inner classes to avoid leakage.

In addition, if you want to call the external class Activity inside the handler, you can point to the activity inside the handler by using a weak reference, so that the unification will not lead to memory leak.

For the anonymous class Runnable, you can also set it to a static class. Because static anonymous classes do not hold references to external classes.

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

V. Summary

<<<<<<<HEAD Although the difference between static classes and non-static classes is not big, it must be understood by Android developers. At least we need to be clear that if an instance of an internal class has a longer lifetime than Activity, we should never use a non-static internal class. The best way to do this is to use static internal classes and then use weak references in that class to point to Activeness.

Links to the original text:

http://www.jianshu.com/p/cb9b4b71a820

Although there is little difference between static and non-static classes, it is important for Android developers to understand. At least we need to be clear that if an instance of an internal class has a longer lifetime than Activity, we should never use a non-static internal class. The best way to do this is to use static internal classes and then use weak references in that class to point to Activeness.

Topics: Android Java