How to prevent Handler memory leakage?

Posted by dennismonsewicz on Tue, 28 Sep 2021 03:22:20 +0200

During the interview with iqiyi yesterday, the interviewer mentioned the use of static Handler. In fact, I didn't think of this method in the project. So here's another summary of the methods to prevent Handler memory leakage?

1. Why does handler cause memory leakage?

This needs to start with the principle of message mechanism. Because there are many introductions to this part of the principle in previous blogs or on the Internet, we will directly enter the topic here.

We know that the process in the Message mechanism is the handler. Finally, a Message is added to the Message queue MessageQueue through enqueueMessage, and then the Looper of each thread will cycle to judge whether there is a Message. If there is a Message, it will take the Message out of the Message queue. Since each Message object is associated with the handler instance object target that sends the Message, when taking out the Message object in the Message queue, you can obtain the handler object through the target, and finally send the Message through the dispatcher Message of the handler. Because the handler is instantiated in the main thread, it can complete a data transfer between the child thread and the main thread.

So where does this process lead to memory leaks?

The answer is given directly here, because if the Message is compared through the when field, it is finally added to an ordered linked list MessageQueue. When our Activity exits, but there are still messages in the Message queue that have not been processed by the Handler in the current Activity, it will lead to memory leakage! Why? Let's take a look at how we usually write non static handlers:

Handler handler = new Handler(Looper.getMainLooper()){
    @Override
    public void handleMessage(@NonNull Message msg) {
        super.handleMessage(msg);
    }
};

Because in the Handler, the handleMessage method is an empty implementation, such as:

public void handleMessage(@NonNull Message msg) {
}

When dealing with messages, we need to implement this method ourselves. Therefore, use the anonymous inner class method above to create an example of Handler.

We know that the anonymous inner class will implicitly hold the example object of the external class. Therefore, the example object of the Handler will actually hold the example of the destroyed Activity. When there are messages to be processed in the message queue, the Handler will not be recycled. The Activity that should have been destroyed will not be recycled. That is, a memory leak.

2. Prevent Handler memory leakage?

We have known the reason through the above, so the corresponding solutions can be as follows.

2.1 using static Handler

Since the life cycle of a static Handler is the application life cycle, when we use a static Handler, we don't need to create a static Handler for each Activity, because this is unreasonable. In the Java language, the instance created by default is a strong reference object, that is, if it is not set to null, the object will never be recycled unless the program terminates.

Well, there is no doubt that this will lead to a lot of waste of space. Many fields are provided in the Message to identify the category of the Message, so the Handler should be reused instead of creating multiple. And the creation process needs to allocate space, which will actually consume a certain efficiency.

Therefore, in consideration of efficiency and space, when using static Handler, try to use singleton mode. For example, the following case:

public class MyHandler {
    // Lock object
    private static final Object mObject = new Object();
    private static Handler mHandler;

    private MyHandler(){}

    public static Handler getHandler(IHandler iHandler){
        if(mHandler == null){
            synchronized (mObject){
                if(mHandler == null){
                    mHandler = new Handler(Looper.getMainLooper()){
                        @Override
                        public void handleMessage(@NonNull Message msg) {
                            iHandler.handleMessage(msg);
                        }
                    };
                }
            }
        }
        return mHandler;
    }
	
	// Define a callback interface
    public interface IHandler{
        void handleMessage(@NonNull Message msg);
    }
}

Then use it again, such as:

public class ThreeActivity extends AppCompatActivity implements MyHandler.IHandler {
    private ProgressBar progressbar;
    private Handler handler;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_three);
        progressbar = findViewById(R.id.progressbar);
        progressbar.setMax(100);

        handler = MyHandler.getHandler(this);

        new Thread(new Runnable() {
            @Override
            public void run() {
                Message msg = new Message();
                msg.obj = 50;
                msg.what = 1;
                handler.sendMessage(msg);
            }
        }).start();
    }

    @Override
    public void handleMessage(@NonNull Message msg) {
        if(msg.what == 1){
            progressbar.setProgress((int) msg.obj);
        }
    }
}

Test results:

2.2 clearing message queue

As we know earlier, the memory leakage of the Activity will be caused when there are unprocessed messages in the message queue. Therefore, when the Activity exits, the current message queue can be emptied to achieve the purpose of prevention.

That is to say, call removeCallbacksAndMessages function in every onDestroy lifecycle method of Activity, for example:

@Override
protected void onDestroy() {
    super.onDestroy();
    handler.removeCallbacksAndMessages(null);
}

2.3 using static Handler + weak reference

The various benefits of using static Handler have been mentioned earlier. Here we continue to use static Handler, but use weak reference in it.

private static class StaticHandler extends Handler{
 private WeakReference<Activity> mWeakReference;
    public StaticHandler(Activity activity){
        mWeakReference = new WeakReference<>(activity);
    }

    @Override
    public void handleMessage(@NonNull Message msg) {
        Activity activity = mWeakReference.get();
        if(activity == null) return;
        // todo processing messages
    }
}

For this code, refer to the blog: Analysis and solution of memory leakage caused by Android Handler.

There is a feeling of sudden openness, because it can be avoided by judging once in handleMessage. If it is defined as static, there is no problem of implicit holding of anonymous internal classes. Moreover, in this static class, the incoming Activity instance object is decorated with a weak reference, which can be recycled during GC, and then judged in handleMessag in time, which is more secure and reliable.

3. Final version

/**
 * Date: 2021 September 28, 2010 10:11:52
 * Author: Dream no
 * Example:
 * MyHandler myHandler = MyHandler.getHandler(this);
 * myHandler.setActivityReference(this);
 * handler = myHandler.getHandler();
 */
public class MyHandler {
    // Lock object
    private static final Object mObject = new Object();
    private Handler mHandler;
    private WeakReference<Activity> mWeakReference;
    private static MyHandler mMyHandler;

    private MyHandler(IHandler iHandler){
        mHandler = new Handler(Looper.getMainLooper()){
            @Override
            public void handleMessage(@NonNull Message msg) {
                Activity activity = mWeakReference.get();
                if(activity != null){
                    iHandler.handleMessage(msg);
                }
            }
        };
    }

    /**
     * Virtual reference, in order to complete logic: it is not processed when the current activity does not exist
     * @param activity
     */
    public void setActivityReference(Activity activity){
        mWeakReference = new WeakReference<>(activity);
    }

    public Handler getHandler(){
        return this.mHandler;
    }

    /**
     * Singleton, for reuse
     * @param iHandler
     * @return
     */
    public static MyHandler getHandler(IHandler iHandler){
        if(mMyHandler == null){
            synchronized (mObject){
                if(mMyHandler == null){
                    mMyHandler = new MyHandler(iHandler);
                }
            }
        }
        return mMyHandler;
    }

    public interface IHandler{
        void handleMessage(@NonNull Message msg);
    }
}

Thanks

Topics: Java Android