Solution to memory leak problem caused by Handler in Android

  • 2020-04-01 03:40:55
  • OfStack

In common Android programming, handlers are often used when performing asynchronous operations and processing returned results. Usually our code does this.


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

However, the above code can actually cause memory leaks, which you will be warned about when using the Android lint tool


In Android, Handler classes should be static or leaks might occur, Messages enqueued on the application thread's MessageQueue also retain their target Handler. If the Handler is an inner class, its outer class will be retained as well. To avoid leaking the outer class, declare the Handler as a static nested class with a WeakReference to its outer class

So there's a little bit of confusion here, where in the code might cause memory leaks, and how? So let's analyze it slowly.

1. When an Android application starts, a Looper instance is automatically created to be used with the main thread. Looper's main job is to process message objects in message queues one at a time. In Android, all the events of the Android framework (such as Activity lifecycle method calls and button clicks) are put into the message and then added to the message queue to be processed by Looper, which handles them one by one. The Looper life cycle in the main thread is as long as the current application.

2. When a Handler is initialized after the main thread, we send a Message with the target Handler to the Message queue processed by Looper, the Message that has been sent already contains a reference to the Handler instance. Only in this way can Looper call Handler#handleMessage(Message) to complete the correct processing of the Message when it is processed by this Message.

3. In Java, both non-static and anonymous inner classes implicitly hold references to their external classes. Static inner classes do not hold references to external classes. Check out the details on this (link: #)

While the above code example is a little hard to detect memory leaks, the following example is pretty obvious


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

Analyze the code above, when we performed the Activity method of finish is delayed message will exist in the main thread before being processed in the message queue for 10 minutes, and this message contains a Handler reference, and the Handler is an anonymous inner class instance, holding the SampleActivity outside references, so it caused the SampleActivity cannot be recovered, to cause the SampleActivity held a lot of resources cannot be recycled, this is what we often say that memory leaks.

Note that the new Runnable above, which is also implemented by the anonymous inner class, also holds a reference to SampleActivity and prevents SampleActivity from being reclaimed.

To solve this problem, the idea is that non-static inner classes don't work, and that when you inherit a Handler, you either put it in a separate class file or you use static inner classes. Because static inner classes do not hold references to external classes, they do not cause memory leaks to external class instances. When you need to call an external Activity in a static inner class, we can use weak references to handle this. There is also a need to set Runnable to a static member property. Note: a static anonymous inner class instance does not hold a reference to an outer class. The code that will not cause memory leak after modification is 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();
  }
}

Actually in a lot of memory leaks in the Android are for use in the Activity not caused by a static inner class, as mentioned in this paper, so when we used to be particularly careful when a static inner class, if its instance holds the object lifecycle is greater than its external class object, so it may lead to memory leaks. Individuals tend to use static classes and weak references to articles to solve this problem.


Related articles: