Last active
November 10, 2015 16:56
-
-
Save nhachicha/e993fe9b5f09f0595ccd to your computer and use it in GitHub Desktop.
Example demonstrating problem you may encounter using the Looper/Handler with unit tests. & a pattern at the end to avoid them
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public void testLooperNotProcessingMessageTestWillNotExit () throws InterruptedException { | |
Handler handler = new Handler() { | |
@Override | |
public void handleMessage(Message msg) { | |
assertEquals(42, msg.what); | |
} | |
}; | |
boolean messageSent = handler.sendEmptyMessage(42); | |
assertTrue(messageSent); | |
} | |
public void testLooperUnreachableCode () throws InterruptedException { | |
Handler handler = new Handler() { | |
@Override | |
public void handleMessage(Message msg) { | |
assertEquals(42, msg.what); | |
} | |
}; | |
boolean messageSent = handler.sendEmptyMessage(42); | |
Looper.loop(); | |
// !!! Unreachable code below !!! | |
assertTrue(messageSent); | |
} | |
public void testLooperQuittingLooper () throws InterruptedException { | |
Handler handler = new Handler() { | |
@Override | |
public void handleMessage(Message msg) { | |
assertEquals(42, msg.what); | |
Looper.myLooper().quit(); | |
} | |
}; | |
boolean messageSent = handler.sendEmptyMessage(42); | |
Looper.loop(); | |
// Looper.prepare(); you can't call/reuse the Looper again | |
// in this test You'll get a RuntimeException: | |
// Only one Looper may be created per thread | |
assertTrue(messageSent); | |
} | |
public void testUsingHandlerThread () throws Throwable { | |
final CountDownLatch signalTestCompleted = new CountDownLatch(1); | |
final HandlerThread backgroundThread = new HandlerThread("bg"); | |
backgroundThread.start(); // start the looper | |
Handler handler = new Handler(backgroundThread.getLooper()) { | |
@Override | |
public void handleMessage(Message msg) { | |
assertEquals("bg", Thread.currentThread().getName()); | |
assertEquals(42, msg.what); | |
signalTestCompleted.countDown(); | |
} | |
}; | |
handler.sendEmptyMessage(42); | |
signalTestCompleted.await(); | |
backgroundThread.quit(); | |
} | |
public void testUsingLooperWithIdleHandler() throws Throwable { | |
final CountDownLatch signalTestCompleted = new CountDownLatch(1); | |
final Throwable[] threadAssertionError = new Throwable[1]; | |
ExecutorService executorService = Executors.newSingleThreadExecutor(); | |
executorService.submit(new Runnable() { | |
@Override | |
public void run() { | |
try { | |
// prepare a Looper for this Thread | |
Looper.prepare(); | |
// register IdleHandler to quit the Looper once all messages have proceeded | |
// Let the first queueIdle invocation pass, because it occurs before the first message is received. | |
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() { | |
@Override | |
public boolean queueIdle() { | |
// this is the last message in our Queue, we can test | |
// any logic we want, we chose simply to complete our test in our case | |
signalTestCompleted.countDown(); | |
return false; // unregister from the future IdleHandler events | |
} | |
}); | |
Handler handler = new Handler() { | |
@Override | |
public void handleMessage(Message msg) { | |
assertEquals(42, msg.what); | |
} | |
}; | |
handler.sendEmptyMessage(42); | |
Looper.loop(); | |
} catch (Throwable e) { | |
threadAssertionError[0] = e; // important to catch exception & AssertionFailedError | |
} | |
} | |
}); | |
// make the InstrumentationThread wait until our test complete | |
signalTestCompleted.await(); | |
if (null != threadAssertionError[0]) { | |
// throw any assertion errors happened in the background thread | |
throw threadAssertionError[0]; | |
} | |
executorService.shutdownNow(); | |
} | |
// Pattern that gives you full control over the Looper, with no side effect on other test | |
// + guarantee to run any finally block | |
public void testLooperRecommendedPatternFinallyGotcha () throws Throwable { | |
final CountDownLatch signalTestCompleted = new CountDownLatch(1); | |
final Throwable[] threadAssertionError = new Throwable[1]; | |
final Looper[] testLooper = new Looper[1]; | |
ExecutorService executorService = Executors.newSingleThreadExecutor(); | |
executorService.submit(new Runnable() { | |
@Override | |
public void run() { | |
InputStream is = null; // expensive resource | |
try { | |
// prepare a Looper for this Thread | |
Looper.prepare(); | |
testLooper[0] = Looper.myLooper(); | |
Handler handler = new Handler() { | |
@Override | |
public void handleMessage(Message msg) { | |
assertEquals(42, msg.what); | |
signalTestCompleted.countDown(); | |
} | |
}; | |
handler.sendEmptyMessage(42); | |
Looper.loop(); | |
} catch (Throwable e) { | |
threadAssertionError[0] = e; // important to catch exception & AssertionFailedError | |
} finally { | |
// this will not run (even if we shutdown the ExecutorService) | |
// unless the Looper is stopped before | |
if (is != null) { | |
try {is.close();} catch (IOException e) {} | |
} | |
} | |
} | |
} | |
); | |
// make the InstrumentationThread wait until our test complete | |
signalTestCompleted.await(); | |
// throw any assertion errors happened in the background thread | |
if(threadAssertionError[0] != null) { | |
throw threadAssertionError[0]; | |
} | |
// before shutting down the executor stop the Looper to give | |
// the finally block a chance to run | |
if (testLooper[0] != null) { | |
testLooper[0].quit(); // or quitSafely for API level >= 18 terminate as soon as all remaining messages | |
// in the message queue that are already due to be delivered have been handled. | |
} | |
executorService.shutdownNow(); | |
} | |
public void testUsingLooperRecommendedPattern () throws Throwable { | |
final CountDownLatch signalTestCompleted = new CountDownLatch(1); | |
final Throwable[] threadAssertionError = new Throwable[1]; | |
ExecutorService executorService = Executors.newSingleThreadExecutor(); | |
executorService.submit(new Runnable() { | |
@Override | |
public void run() { | |
try { | |
// prepare a Looper for this Thread | |
Looper.prepare(); | |
Handler handler = new Handler() { | |
@Override | |
public void handleMessage(Message msg) { | |
assertEquals(42, msg.what); | |
signalTestCompleted.countDown(); | |
} | |
}; | |
handler.sendEmptyMessage(42); | |
Looper.loop(); | |
} catch (Throwable e) { | |
threadAssertionError[0] = e; // important to catch exception & AssertionFailedError | |
} | |
} | |
}); | |
// make the InstrumentationThread wait until our test complete | |
signalTestCompleted.await(); | |
if (null != threadAssertionError[0]) { | |
// throw any assertion errors happened in the background thread | |
throw threadAssertionError[0]; | |
} | |
executorService.shutdownNow(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment