Memory leakage caused by Handler in Android
In common Android programming, Handler is often used to perform asynchronous operations and process returned results. Our code is usually implemented in this way.
- public class SampleActivity extends Activity {
-
- private final Handler mLeakyHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- // ...
- }
- }
- }
However, the above Code may cause memory leakage. When you use the Android lint tool, you will receive such a warning.
In Android, Handler classes shoshould 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
We can see that there may still be some unclear information, where may cause memory leakage in the code, and how can it cause memory leakage? Let's take a closer look.
1. When an Android Application is started, a logoff instance that is used by the main thread is automatically created. Logoff processes message objects in message queues one by one. In Android, all Android framework events, such as Activity life cycle method calls and button clicks, are put into the message and then added to the Message Queue to be processed by logoff, logoff is responsible for processing data one by one. The logoff lifecycle in the main thread is as long as that of the current application.
2. when a Handler is initialized in the main thread, we send a message whose target is the Handler to The logoff message queue, in fact, a sent Message contains a reference of a Handler instance. Only in this way can logoff call Handler # handleMessage (Message) to complete correct Message processing when processing the Message.
3. in Java, non-static internal classes and anonymous internal classes both implicitly hold references to their external classes. Static internal classes do not hold external class references. For details, see the Java: "invalid" private modifier.
Indeed, the above sample code is a little hard to detect memory leaks, so the following example is very 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 execute the Activity finish method, delayed messages will exist in the main thread message queue for 10 minutes before being processed, this message contains the reference of Handler, while Handler is an anonymous internal class instance, which holds the reference of SampleActivity outside, so SampleActivity cannot be recycled, as a result, many resources held by SampleActivity cannot be recycled. This is what we often call Memory leakage.
Note that the new Runnable mentioned above is implemented by an anonymous internal class. It also holds a reference to SampleActivity and prevents SampleActivity from being recycled.
To solve this problem, the idea is that non-static internal classes are not applicable. When Handler is inherited, they are either stored in a separate class file or static internal classes. Because static internal classes do not hold external class references, they do not cause memory leakage of external class instances. When you need to call an external Activity in a static internal class, we can use weak references for processing. In addition, you also need to set Runnable to static member attributes. Note: A static anonymous internal class instance does not hold external class references. The modified code will not cause memory leakage:
- 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();
- }
- }
In fact, many memory leaks in Android are caused by the use of non-static internal classes in the Activity, as mentioned in this Article, therefore, when using non-static internal classes, pay special attention to them. If the life cycle of the objects held by the instance is greater than that of the external class objects, memory leakage may occur. I personally prefer to use static classes and weak references to solve this problem.
Translation information
- How to Leak a Context: Handlers & Inner Classes