Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

This message cannot be recycled because it is still in use

I'm trying to use this article to create asynchronous UDP socket.

So I've this code:

import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;

import java.net.DatagramSocket;
import java.net.SocketException;

public class UdpThread
    extends HandlerThread {

    private static final String TAG = "UDP";
    private final Handler uiHandler, workerHandler;
    private final DatagramSocket socket = new DatagramSocket();

    public UdpThread(final Handler uiHandler, final String hostname, final int port) throws SocketException {
        super(TAG);
        this.uiHandler = uiHandler;
        start();
        workerHandler = new Handler(getLooper(), new Handler.Callback() {
            @Override
            public boolean handleMessage(final Message msg) {
                /*
                if (msg.what == port && msg.obj == hostname) {
                    final InetSocketAddress address = new InetSocketAddress(hostname, port);
                    Log.d(TAG, "Connecting to " + address);
                    try {
                        socket.connect(address);
                    } catch (SocketException se) {
                        throw new RuntimeException(se);
                    }
                }
                */
                msg.recycle(); //java.lang.IllegalStateException: This message cannot be recycled because it is still in use.
                return true;
            }
        });
        workerHandler.obtainMessage(port, hostname).sendToTarget();
    }
}

But when I run the code, I get the mentioned java.lang.IllegalStateException: This message cannot be recycled because it is still in use. when trying to recycle the message. Why is that and how to solve it and prevent memory leaks?

like image 809
Pitel Avatar asked May 17 '17 08:05

Pitel


2 Answers

Well first of all lets see how Message recycle() method works.

public void recycle() {
    if (isInUse()) {
        if (gCheckRecycle) {
            throw new IllegalStateException("This message cannot be recycled because it "
                    + "is still in use.");
        }
        return;
    }
    recycleUnchecked();
}

So you are getting IllegalStateException if it is in use

isInUse() just checks flag and looks like:

boolean isInUse() {
        return ((flags & FLAG_IN_USE) == FLAG_IN_USE);
    }

And when we try to read about that flag we see description:

If set message is in use.

This flag is set when the message is enqueued and remains set while it is delivered and afterwards when it is recycled. The flag is only cleared when a new message is created or obtained since that is the only time that applications are allowed to modify the contents of the message.

It is an error to attempt to enqueue or recycle a message that is already in use.

So what we have

  1. You cant recycle message until its "in use"
  2. It is "in use" until new message obtained or created

How to solve the problem

There is method recycleUnchecked() inside Message class to recycle message object even if it is in use.Thats what you need! Description of it:

Recycles a Message that may be in-use.

Used internally by the MessageQueue and Looper when disposing of queued Messages.

Worst thing that it uses internally and has package access. Good thing that it uses internally when you call:

handler.removeMessages(int what)

So I guess final solution is:

replace

msg.recycle();

to

try {
     msg.recycle(); //it can work in some situations
} catch (IllegalStateException e) {
     workerHandler.removeMessages(msg.what); //if recycle doesnt work we do it manually
}
like image 90
Andrey Danilov Avatar answered Nov 03 '22 13:11

Andrey Danilov


You shouldn't call msg.recycle() yourself, message is recycled automatically by Looper after it has been dispatched/processed (after your handleMessage() returns), see source code.

like image 35
Robyer Avatar answered Nov 03 '22 14:11

Robyer