Android's "Memory Overflow Warning Caused by Handler Internal Classes"

Posted by jnewing on Wed, 10 Jul 2019 01:04:34 +0200

When using Handler asynchronous operation and returning the result to update the UI, it is usually written as follows:

public class SampleActivity extends Activity {

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

It seems normal, but in fact, the above code may leak memory together, using Android Lint will give a warning:

This Handler class should be static or leaks might occur
 In Android, Handler classes should be statically modified, otherwise memory leaks may occur.

Where can memory leaks occur in code, and how can memory leaks occur? Let's analyze it slowly.

1. When the Android application is first started, the framework creates a Looper object in the UI thread of the application. Looper implements a simple message queue and processes messages in the queue one by one. All events of the application (such as Activity lifecycle callback methods, button clicks, etc.) are placed in Looper's message queue as a message object and then executed one by one. The Looper of the UI thread exists throughout the life cycle of the application.

2. When a Handler object is created in the UI thread, it is associated with the message queue of Looper in the UI thread. Messages sent to this message queue hold a reference to the Handler, so that when Looper finally processes the message, the framework calls the Handler handleMessage (Message) method to process the specific logic.

3. In Java, non-static internal classes or anonymous classes implicitly hold references to their external classes, while static internal classes do not. ps: Here's an introduction to the principle of implicit holding citation. Detailed Java: private modifier for "invalid"

Let's look at 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();
  }
}

1. When activity is finish ed, the delayed message will still survive in the message queue of the UI thread until it is processed in 10 minutes.

2. This message holds a reference to the activity's Handler, which implicitly holds a reference to its external class (SampleActivity in this case). This reference will persist until the message is processed, so the garbage collection mechanism cannot reclaim the activity, and memory leaks occur.

3. Anonymous Runnable subclasses can also lead to memory leaks. Non-static anonymous classes implicitly hold references to external classes, so context is leaked.

Solution:

(1) Implementing a subclass of Handler in a new class file or modifying the inner class with static.
(2) Static internal classes do not hold references to external classes, so activity is not leaked. If you want to call methods of external activity classes in Handler, you can let Handler hold weak references to external activity classes, so there is no risk of leakage of activity.
(3) Regarding the leakage caused by anonymous classes, we can use static to modify the anonymous class object to solve this problem, because static anonymous classes do not hold references to their external classes.

As follows:

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

summary

1. This article describes the difference between static and non-static internal classes, which hold references to external classes.
2. If the lifetime of the holding object of an internal class instance is larger than that of its external class object, it may lead to memory leak. For example, to instantiate an internal class object beyond the activity lifecycle, avoid using non-static internal classes. It is recommended to use static internal classes and hold weak references to external classes in internal classes.

Reference material

1,How to Leak a Context: Handlers & Inner Classes
2,This Handler class should be static or leaks might occur: IncomingHandler

Topics: Android Java