Consider the following code:
public class SampleActivity extends Activity { private final Handler mLeakyHandler = new Handler() { @Override public void handleMessage(Message msg) { // ... } } }
Although this is not obvious, this code can lead to a massive memory leak. Android Lint will display the following warning:
In Android, Handler classes should be static or leaks might occur.
But where exactly does the leak occur and how can this happen? Allows you to determine the source of the problem by first documenting what we know:
- When the Android application first starts, the environment creates a Looper object for the main application stream. Looper implements a simple message queue, processing Message objects in a loop one after another. All major events of the application infrastructure (such as activity lifecycle method calls, button clicks, etc.) are contained within message objects that are added to the Loopers message queue and processed one after another. Looper core threads exist throughout the application life cycle.
- When a handler is created in the main thread, it is associated with the Loopers message queue. Messages sent to the message queue will contain a reference to the handler so that the structure can call Handler # handleMessage (Message) when Looper ultimately processes the message.
- In Java, non-static inner and anonymous classes contain implicit references to their outer class. Static inner classes, on the other hand, do not.
So where exactly is the memory leak? This is very subtle, but as an example, consider 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 action is completed, the pending message will continue to live in the message queue of the main threads 10 minutes before it is processed. The message contains a link to the activity handler, and the handler holds an implicit reference to its external class (SampleActivity, in this case). This link will remain until the message is processed, which will prevent the collection of the activity context and the leakage of all application resources. Note that the same is true with the anonymous Runnable class on line 15. Non-static instances of anonymous classes contain an implicit reference to their outer class, so this context will leak.
To fix the problem, subclass Handler in a new file or use a static inner class instead. Static inner classes do not contain an implicit reference to their outer class, so activity will not proceed. If you need to call external activity methods from a handler, the handler must hold the WeakReference for the action so that you do not accidentally leak out of context. To fix the memory leak that occurs when creating an anonymous Runnable class, we make the variable a static field of the class (since static instances of anonymous classes do not contain 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, 1000 * 60 * 10); // Go back to the previous Activity. finish(); } }
The difference between static and non-stationary inner classes is subtle, but this is what every Android developer should understand. What is the point? Avoid using non-static inner classes in action if instances of the inner class can survive the activity life cycle. Instead, prefer static inner classes and keep a weak reference to activity inside.