36

I want to set up a HandlerThread from the GUI thread. Then some time later, when a button is clicked on the GUI, it runs callHello(), which then send a message to a HelloLogger object residing on the non-GUI thread which asynchronously logs "Hello World". I have tried a number of things, some block indefinitely, some never receive the message, etc etc. The code below is more or less as close as I have got, please could someone modify it to work?

public class HandlerThreadExample {

    private MyHandlerThread mMyHandlerThread;
    private Looper mLooper;
    private Handler mHandler;

    public HandlerThreadExample(){
        mMyHandlerThread = new MyHandlerThread();
        mMyHandlerThread.start();
        mLooper = mMyHandlerThread.getLooper();
    }
    public void callHello() {
        mHandler.sendEmptyMessage(1);
    }
    private class MyHandlerThread extends HandlerThread {
        private HelloLogger mHelloLogger;
        private Handler mHandler;
        public MyHandlerThread() {
            super("The MyHandlerThread thread", HandlerThread.NORM_PRIORITY);
        }
        public void run (){
            mHelloLogger = new HelloLogger();
            mHandler = new Handler(getLooper()){
                public void handleMessage(Message msg){
                    mHelloLogger.logHello();
                }
            };
            super.run();
        }
    }
    private class HelloLogger {
        public HelloLogger (){
        }
        public void logHello(){
            Log.d("HandlerThreadExample", "Hello World");
        }
    }
}

Best examples found:

At least now I can close the damned tabs

Solution courtesy of help from pskink

public class HandlerThreadExample2 {
    private static int MSG_START_HELLO = 0;
    private static int MSG_HELLO_COMPLETE = 1;
    private HandlerThread ht;
    private Handler mHtHandler;
    private Handler mUiHandler;
    private boolean helloReady = false;
    public HandlerThreadExample2(){
        ht = new HandlerThread("The new thread");
        ht.start();
        Log.d(App.TAG, "UI: handler thread started");
        mUiHandler = new Handler(){
            public void handleMessage(Message msg){
                if (msg.what == MSG_HELLO_COMPLETE){
                    Log.d(App.TAG, "UI Thread: received notification of sleep completed ");
                    helloReady = true;              }
            }
        };
        mHtHandler = new Handler(ht.getLooper()){
            public void handleMessage (Message msg){
                if (msg.what == MSG_START_HELLO){
                    Log.d(App.TAG, "handleMessage " + msg.what + " in " + Thread.currentThread() + " now sleeping");
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Log.d(App.TAG, "Woke up, notifying UI thread...");
                    mUiHandler.sendEmptyMessage(MSG_HELLO_COMPLETE);
                }
            }
        };
    }
    public void sendLongHello(){
        if (helloReady){
            Log.d(App.TAG, "sending hello " + Thread.currentThread());      
            mHtHandler.sendEmptyMessage(MSG_START_HELLO);
            helloReady = false;
        } else {
            Log.e(App.TAG, "Cannot do hello yet - not ready");
        }
    }
}
Community
  • 1
  • 1
Jodes
  • 14,118
  • 26
  • 97
  • 156
  • 4
    Think you can do that in 4 lines of code using https://github.com/greenrobot/EventBus – cYrixmorten Aug 02 '14 at 11:09
  • Very interesting, thank you, will look into that another time. +1. But I think I should walk before I run – Jodes Aug 02 '14 at 11:13
  • 1
    No problem but to be completely honest I gave up on using Handlers and Messages even though I have been writing a lot of Android code. No need to do it complicated when it can be done very simple, readable and maintainable IMO :) – cYrixmorten Aug 02 '14 at 11:15
  • Lord knows, I know where you're coming from!!!! Something tells me this is going to be a bounty question lol – Jodes Aug 02 '14 at 11:24
  • 1) create a new HandlerThread, 2) start() it, 3) create a new Handler. using ht.getLooper() as a Looper parameter, 4) send a Message – pskink Aug 02 '14 at 11:38
  • @cYrixmorten Handlers and Messages are used by android code very often, in fact Messages play fundamental role in the android event handling via a Looper – pskink Aug 02 '14 at 11:46
  • @pskink Not arguing against that at all. It is just simpler and much easier to maintain using EventBus, I think. – cYrixmorten Aug 02 '14 at 11:51
  • @pskink, AFAIK I already tried that, and played about with it and even then no luck, there are so many things that can be gotten wrong, I desperately need a full working example :( – Jodes Aug 02 '14 at 15:43
  • @Jodes see my answer below – pskink Aug 03 '14 at 06:12

2 Answers2

61

This is a working example:

HandlerThread ht = new HandlerThread("MySuperAwesomeHandlerThread");
ht.start();
Handler h = new Handler(ht.getLooper()) {
    public void handleMessage(Message msg) {
        Log.d(TAG, "handleMessage " + msg.what + " in " + Thread.currentThread());
    };
};
for (int i = 0; i < 5; i++) {
    Log.d(TAG, "sending " + i + " in " + Thread.currentThread());
    h.sendEmptyMessageDelayed(i, 3000 + i * 1000);
}

UPDATE:

Make two class fields:

Handler mHtHandler;
Handler mUiHandler;

and try this:

HandlerThread ht = new HandlerThread("MySuperAwsomeHandlerThread");
ht.start();
Callback callback = new Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        if (msg.what == 0) {
            Log.d(TAG, "got a meaasage in " + Thread.currentThread() + ", now sleeping... ");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Log.d(TAG, "woke up, notifying ui thread...");
            mUiHandler.sendEmptyMessage(1);
        } else
        if (msg.what == 1) {
            Log.d(TAG, "got a notification in " + Thread.currentThread());
        }
        return false;
    }
};
mHtHandler = new Handler(ht.getLooper(), callback);
mUiHandler = new Handler(callback);
mHtHandler.sendEmptyMessageDelayed(0, 3000);

You can of course get rid of Callback interface and create two Handlers with overridden handleMessage method...

Alexander Farber
  • 21,519
  • 75
  • 241
  • 416
pskink
  • 23,874
  • 6
  • 66
  • 77
  • +1, thank you. I have edited my question, adding an example which attempts to integrate your answer. I still don't know how the GUI thread can tell when the worker thread has finished an operation (without blocking, obviously). What do I need to add to have helloComplete() called? Do I need to make/get another Handler somewhere? – Jodes Aug 03 '14 at 09:17
  • @pskink can you possibly explain how the callback and looper run in parallel, is it to do with some form of scheduler? – Chris Stryczynski Dec 31 '15 at 13:48
  • 1
    @Chris the best way of learning that is to run my second snippet of code – pskink Dec 31 '15 at 14:07
  • How do you expect this to work? start() is non-blocking, so getLooper() in next line may return null. – Yaroslav Mytkalyk Jun 29 '16 at 09:41
  • 1
    @YaroslavMytkalyk no, it cannot, read `HandlerThread#getLooper` documentation – pskink Jun 29 '16 at 10:19
1

The issue you are seeing is because your outer class is using a private mHandler field and so does your HandlerThread. The outer class' field is not initialized. You don't need the inner mHandler. The outer class can crate a handler from the looper you grab right after calling start().

Larry Schiefer
  • 15,687
  • 2
  • 27
  • 33