1

I'm sending a message to a Handler which is associated to it's own Thread. In the handleMessage method I try to update the UI with the content of the message using runOnUiThread. This works fine if take the message obj parameter from handleMessage and assign it to a new final variable. But if I don't use this assignment and take msg.obj directly in the runnable the obj variable is null even though the msg reference is having the same id when checking the msg reference passed to handleMessage before calling runOnUiThread.

Why is that happening?

This is working:

bt01.setOnClickListener(new View.OnClickListener() {
     @Override
     public void onClick(View view) {
         Message msg = new Message();
         msg.obj = new Data("Hi im the message");
         mHandler.sendMessage(msg);
     }
 });

class LooperThread extends Thread {
     public void run() {
        Looper.prepare();
        mHandler = new Handler() {
            public void handleMessage(final Message msg) {
                final Object messageString = msg.obj;
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        tv01.setText(((Data)messageString).getMessage());
                    }
                });
            }
        };
        Looper.loop();
    }
}

This is not working:

public void handleMessage(final Message msg) {                    
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        tv01.setText(((Data)msg.obj).getMessage());
                    }
                });
            }
Logarith
  • 690
  • 1
  • 7
  • 27
  • first of all use `HandlerThread` - no need to reinvent the wheel, second see https://stackoverflow.com/a/25096981/2252830 – pskink May 13 '18 at 10:10
  • Smart android developer use BroadcastReceiver, RxJava or Interface Callbacks for this purpose. – Khemraj Sharma May 13 '18 at 10:17
  • 1
    @Khemraj `BroadcastReceiver` ? its like killing a fly with a cannon / sledgehammer – pskink May 13 '18 at 10:22
  • Tell me your requirement, i would give you best approach for that. – Khemraj Sharma May 13 '18 at 10:25
  • It's just for learning how Handler, Looper etc. work. I just want to understand why obj is null if not directly assigned to a final variable. – Logarith May 13 '18 at 10:35
  • because `Message`s are reused - they go to some kind of cache/pool for future reusing – pskink May 13 '18 at 10:40
  • using a HandlerThread has the same behaviour when overriding Handler.Callback handleMessage – Logarith May 13 '18 at 11:07
  • did you see the code i posted? there are two `Handler`s and one `Handler.Callback` (of course you can also use two custom `Handler`s as well and override their `handleMessage` method) and this is how you should handle your messages and not by using some "floating" final variable – pskink May 13 '18 at 11:27
  • tried your solution now with two custom Handlers, because calling the same callback with different behaviour for handlers with if/else construct doesn't look like good code. It worked but I had to make a copyFrom(message) in the first handler to send the message to the ui handler. (got an error, that message is already in use) Looks like runOnUiThread() seems broken due to pooling when using it with Message data. Not really obvious. – Logarith May 13 '18 at 11:43
  • `"[...] because calling the same callback with different behaviour for handlers with if/else construct doesn't look like good code"` i dont think so, search for any `Handler` in android source code and you will see that almost any `Handler` handles multiple actions depending on `Message#what` field – pskink May 14 '18 at 05:38
  • instead of obtaining empty `Message` and calling `copyFrom` just use `Message.obtain(message)` like [here](https://pastebin.com/HSZt52Mz) – pskink May 14 '18 at 05:47
  • yes you're surely right, that a Handler can handle multiple actions depending on Message#what. But in your example the if/else depends on which handler it is called on. I think both message types should be make sense for the ui and the workerThread. Imagine what would happen if you send sendEmptyMessage(0) to the uiHandler. That would be a nasty bug and it isn't obvious before. – Logarith May 14 '18 at 17:51
  • ok its implementation detail, btw you can always use `Message#getTarget` to determine where to handle your `Message` – pskink May 14 '18 at 19:07

1 Answers1

2

My guess is that the operating system is reusing these Message objects, rather than creating new ones. By the time your Runnable starts running on the UI thread, the Message has been returned to the pool and its fields have been set to null. The existence of obtainMessage() supports this hypothesis.

Thomas
  • 174,939
  • 50
  • 355
  • 478
  • 2
    I looked into the Message source code, it uses a pool when calling obtain() and the looper is calling recycle() which sets obj to null after dispatching the message. – Logarith May 13 '18 at 11:08