Inspired by @Josh Guilfoyle's answer, I decided to try to use reflection to get access to what I needed in order to make my own non-blocking and non-quitting Looper.loop()
.
/**
* Using reflection, steal non-visible "message.next"
* @param message
* @return
* @throws Exception
*/
private Message _next(Message message) throws Exception {
Field f = Message.class.getDeclaredField("next");
f.setAccessible(true);
return (Message)f.get(message);
}
/**
* Get and remove next message in local thread-pool. Thread must be associated with a Looper.
* @return next Message, or 'null' if no messages available in queue.
* @throws Exception
*/
private Message _pullNextMessage() throws Exception {
final Field _messages = MessageQueue.class.getDeclaredField("mMessages");
final Method _next = MessageQueue.class.getDeclaredMethod("next");
_messages.setAccessible(true);
_next.setAccessible(true);
final Message root = (Message)_messages.get(Looper.myQueue());
final boolean wouldBlock = (_next(root) == null);
if(wouldBlock)
return null;
else
return (Message)_next.invoke(Looper.myQueue());
}
/**
* Process all pending Messages (Handler.post (...)).
*
* A very simplified version of Looper.loop() except it won't
* block (returns if no messages available).
* @throws Exception
*/
private void _doMessageQueue() throws Exception {
Message msg;
while((msg = _pullNextMessage()) != null) {
msg.getTarget().dispatchMessage(msg);
}
}
Now in my tests (which need to run on the UI thread), I can now do:
@UiThreadTest
public void testCallbacks() throws Throwable {
adapter = new UpnpDeviceArrayAdapter(getInstrumentation().getContext(), upnpService);
assertEquals(0, adapter.getCount());
upnpService.getRegistry().addDevice(createRemoteDevice());
// the adapter posts a Runnable which adds the new device.
// it has to because it must be run on the UI thread. So we
// so we need to process this (and all other) handlers before
// checking up on the adapter again.
_doMessageQueue();
assertEquals(2, adapter.getCount());
// remove device, _doMessageQueue()
}
I'm not saying this is a good idea, but so far it's been working for me. Might be worth trying out! What I like about this is that Exceptions
that are thrown inside some hander.post(...)
will break the tests, which is not the case otherwise.