In common Android programming, Handler is often used when performing asynchronous operations and processing returned results. Usually our code is implemented like this.
Copy the code code as follows:
public class SampleActivity extends Activity {
private final Handler mLeakyHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// ...
}
}
}
However, in fact, the above code may cause memory leaks. When you use the Android lint tool, you will get such a warning.
Copy the code code as follows:
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
Seeing this, you may still be confused. Where in the code may cause a memory leak, and how may it cause a memory leak? Then let's analyze it slowly.
1. When an Android application starts, a Looper instance is automatically created for use by the application's main thread. The main job of Looper is to process the message objects in the message queue one by one. In Android, all events of the Android framework (such as Activity life cycle method calls and button clicks, etc.) are put into messages, and then added to the message queue to be processed by Looper, and Looper is responsible for processing them one by one. The life cycle of the Looper in the main thread is as long as the current application.
2. When a Handler is initialized in the main thread and we send a message targeting this Handler to the message queue processed by Looper, the message actually sent already contains a reference to a Handler instance. Only in this way is Looper processing Only when this message is received can Handler#handleMessage(Message) be called to complete the correct processing of the message.
3. In Java, non-static inner classes and anonymous inner classes implicitly hold references to their outer classes. Static inner classes do not hold references to outer classes. For more information about this, please see Chat Java: "Invalid" private modifier
It is true that the above code example is a bit difficult to detect the memory leak, then the following example is very obvious
Copy the code code as follows:
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();
}
}
Analyzing the above code, when we execute the activity's finish method, the delayed message will exist in the main thread message queue for 10 minutes before being processed, and this message contains a reference to the Handler, and the Handler is an anonymous The instance of the inner class holds a reference to the external SampleActivity, so this causes the SampleActivity to be unable to be recycled. As a result, many resources held by the SampleActivity cannot be recycled. This is what we often call a memory leak.
Note that the new Runnable above is also implemented by an anonymous inner class. It also holds a reference to SampleActivity and prevents SampleActivity from being recycled.
To solve this problem, the idea is not to use non-static inner classes. When inheriting Handler, either place it in a separate class file or use a static inner class. Because the static inner class does not hold a reference to the outer class, it will not cause memory leaks of the outer class instance. When you need to call an external Activity in a static inner class, we can use weak references to handle it. In addition, Runnable also needs to be set as a static member attribute. Note: A static anonymous inner class instance does not hold a reference to the outer class. The code that will not cause memory leaks after modification is as follows:
Copy the code code 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();
}
}
In fact, many memory leaks in Android are caused by the use of non-static internal classes in Activity, as mentioned in this article, so we must pay special attention when using non-static internal classes. If its instance If the life cycle of the held object is greater than its outer class object, it may lead to memory leaks. Personally, I tend to use the article's static class and weak reference methods to solve this problem.