Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I ensure another Thread's Handler is not null before calling it?

My program threw a NullPointerException the other day when it tried to use a Handler created on another thread to send that thread a message. The Handler created by the other thread was not yet created, or not yet visible to the calling thread, despite the calling thread having already called start on the other thread. This only happens very rarely. Almost every test run does not get the exception.

I was wondering what the best way is to avoid this problem for sure with minimal complication and performance penalty. The program is a game and very performance sensitive, especially once it is running. Therefore I try to avoid using synchronization after setup, for example, and would prefer to avoid spinning on a variable at any time.

Background:
In Android the Handler class may be used to "enqueue an action to be performed on a different thread than your own". Documentation here:
http://developer.android.com/intl/de/reference/android/os/Handler.html

The Handler must be created on the thread where it will be used. So creating it in the constructor of a thread, which is run by the thread creating that thread, is not an option.

When the Handler is for a thread other than the UI thread, the Looper class must also be used:
http://developer.android.com/intl/de/reference/android/os/Looper.html

The documentation gives this example of using the two classes for this purpose:

class LooperThread extends Thread {
    public Handler mHandler;

    public void run() {
        Looper.prepare();

        mHandler = new Handler() {
            public void handleMessage(Message msg) {
                // process incoming messages here
            }
        };

        Looper.loop();
    }
}

My very ugly workaround currently looks like this:

public class LooperThread extends Thread {

    public volatile Handler mHandler;

    public final ArrayBlockingQueue<Object> setupComplete = new ArrayBlockingQueue<Object>(1);

    public void run() {
        Looper.prepare();

        mHandler = new Handler() {
            public void handleMessage(Message msg) {
                // process incoming messages here
            }
        };

        setupComplete();

        Looper.loop();
    }

    public void waitForSetupComplete() {
        while ( true ) {
            try {
                setupComplete.take();
                return;
            } catch (InterruptedException e) {
                //Ignore and try again.
            }
        }
    }

    private void setupComplete() {
        while( true ) {
            try {
                setupComplete.put(new Object());
                return;
            } catch (InterruptedException e) {
                //Ignore and try again.
            }        
        }
    }

}

With the code in the creating thread looking like this:

    LooperThread otherThread = new LooperThread();
    otherThread.start();        
    otherThread.waitForSetupComplete();
    otherThread.mHandler.sendEmptyMessage(0);

Are there any better solutions? Thanks.

like image 864
Lance Nanek Avatar asked Feb 02 '10 20:02

Lance Nanek


3 Answers

I'd go with the classic wait/notify

public class LooperThread extends Thread {

    private Handler mHandler;

    public void run() {
        Looper.prepare();

        synchronized (this) {
            mHandler = new Handler() {
                public void handleMessage(Message msg) {
                    // process incoming messages here
                }
            };
            notifyAll();
        }

        Looper.loop();
    }

    public synchronized Handler getHandler() {
        while (mHandler == null) {
            try {
                wait();
            } catch (InterruptedException e) {
                //Ignore and try again.
            }
        }
        return mHandler;
    }
}

Handler returned from getHandler can then be used many times without invoking synchronized getHandler.

like image 98
Andrey Ashikhmin Avatar answered Oct 06 '22 01:10

Andrey Ashikhmin


Preparing a Looper can block for a while, so I imagine you're hitting a condition where prepare() takes a moment to complete, thus mHandler is still undefined.

You could have your Thread extend HandlerThread, though even then you still have to wait to ensure the Looper has initialised. Perhaps something like this might work, where you have the Handler defined separately, but utilising the Looper of your custom thread.

Maybe.

private void setUp() {
    mHandlerThread = new CustomThread("foo", Process.THREAD_PRIORITY_BACKGROUND);
    mHandlerThread.start();

    // Create our handler; this will block until looper is initialised
    mHandler = new CustomHandler(mHandlerThread.getLooper());
    // mHandler is now ready to use
}

private class CustomThread extends HandlerThread {
    public void run() {
        // ...
    }
}   

private class CustomHandler extends Handler {
    CustomHandler(Looper looper) {
        super(looper);
    }

    @Override
    public void handleMessage(Message msg) {
        // ...
    }
}
like image 24
Christopher Orr Avatar answered Oct 05 '22 23:10

Christopher Orr


I just want to add that the checked answer is the best one but if you test it like that is not going to work becouse you need to call super on run methode since it's in charge of preparing the looper so the code should be like this:

private void setUp() {
  mHandlerThread = new CustomThread("foo", Process.THREAD_PRIORITY_BACKGROUND);
  mHandlerThread.start();

  // Create our handler; this will block until looper is initialised
  mHandler = new CustomHandler(mHandlerThread.getLooper());
  // mHandler is now ready to use
}

private class CustomThread extends HandlerThread {
   public void run() {
    super.run() // <- VERY IMPORTANT OTHERWISE IT DOES NOT WORK
    // your code goes here
   }
}   

private class CustomHandler extends Handler {
CustomHandler(Looper looper) {
    super(looper);
}

 @Override
 public void handleMessage(Message msg) {
    // ...
 }

}

like image 23
Xenione Avatar answered Oct 06 '22 01:10

Xenione