This message cannot be recycled because it is still in use
Asked Answered
E

3

6

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?

Ensample answered 17/5, 2017 at 8:53 Comment(2)
I don't think you have to use recycleLoseff
This seems correct. even when I start spamming the messages, the memory consumption seems flat.Ensample
H
6

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
}
Head answered 17/5, 2017 at 10:19 Comment(0)
P
4

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.

Palaestra answered 9/11, 2018 at 17:40 Comment(0)
O
0

Try to use AsyncTask to delete the message when the handler thread finish to proceed it.

//[..]
        //synchronized with the handler thread
        @Override
        public boolean handleMessage(final Message msg) {
            new MessageDestructor().execute(msg);
            return true;
        }
//[..]
private class MessageDestructor extends AsyncTask<Message, Void, Void> {
    Message msg;
    @Override
    protected String doInBackground(Message... params) {
        msg = (Message) params[0]; 
        return null;
    }

    @Override
    protected void onPostExecute(Void result) {
       msg.recycle(); //synchronized with the main thread
    }

    @Override
    protected void onPreExecute() {
    }

    @Override
    protected void onProgressUpdate(Void... values) {
    }
}
Onetoone answered 17/5, 2017 at 9:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.