On iOS, if I want my current thread of execution to wait (ie. block) and the main loop to run so that the thread of execution next in the main queue can execute, I invoke:
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate date]];
How would I go about doing the equivalent on Android?
This is indeed possible to do in Android. Shachar's answer is on the right track. The problem is not that the main loop will block (unless the code was executed on the main thread, but that's not what the question is proposing). The problem is that the other thread doesn't block, but is simply looping and burning CPU cycles in the while loop. Here is a blocking run on main method I use in my app:
/**
* Runs the runnable on the main UI thread. If called from a thread other than the UI thread,
* this method will block the calling thread and return only after the runnable has completed
* execution on the main UI thread.
* @param runnable Runnable to run on the main UI thread
*/
public static void blockingRunOnMain(Runnable runnable) {
if (Looper.myLooper() == Looper.getMainLooper()) { // Already on UI thread run immediately
runnable.run();
}
else { // Queue to run on UI thread
final MainRunMonitor lock = new MainRunMonitor();
Handler mainHandler = new Handler(Looper.getMainLooper());
mainHandler.post(runnable);
// Task to notify calling thread when runnable complete
mainHandler.post(new Runnable() {
@Override
public void run() {
synchronized (lock) {
lock.mRunComplete = true;
lock.notify();
}
}
});
// Block calling thread until runnable completed on UI thread
boolean interrupted = false;
try {
synchronized (lock) {
while (!lock.mRunComplete) {
try {
lock.wait();
} catch (InterruptedException e) {
// Received interrupt signal, but still haven't been notified, continue waiting
interrupted = true;
}
}
}
} finally {
if (interrupted) {
Thread.currentThread().interrupt(); // Restore interrupt to be used higher on call stack (we're not using it to interrupt this task)
}
}
}
}
MainRunMonitor
is a simple class, in my case a private inner class to the class that implements blockingRunOnMain()
:
/**
* Monitor to lock calling thread while code is executed on UI thread.
*/
private static class MainRunMonitor {
private boolean mRunComplete = false;
}
blockingRunOnMain()
is used by passing it a Runnable
to run on the main thread:
blockingRunOnMain(new Runnable() {
@Override
public void run() {
workToDoSynchronouslyOnMain();
}
});
The first part of the blockingRunOnMain()
method checks if the method is being called from the main thread and if so, simply executes the code immediately. Since the function of blockingRunOnMain()
is to synchronously run the Runnable
code before the method returns, this will have this same result even if called from the main thread itself.
If the method is called from a thread other than the main thread, we then post the Runnable
to a Handler
which is bound to the main thread's Looper
. After posting the Runnable
parameter, we then post another Runnable
that will execute after the Runnable
parameter completes execution, since the Handler
executes posted Message
s and Runnable
s in order. This second Runnable
serves to notify the blocked thread that the work has been completed on the main thread.
After posting the second Runnable
we now block the background thread and wait until we're notified. It's important to synchronize the operations performed on lock
so that the operations are atomic on each thread.
The background thread calls wait()
on the monitor and waits until mRunComplete == true
. If it gets an InterruptedException
, it's important to continue waiting and restore the interrupted state of the thread after we're done, since we're not using the interrupt mechanism ourselves to cancel our task, restoring it allows another method higher on the call stack to handle the interrupt. See "Dealing with InterruptedException".
When the Runnable
parameter has completed execution and the second posted Runnable
executes, it simply sets mRunComplete
to true and notifies the blocked thread to continue execution, which finding mRunComplete == true
now returns from blockingRunOnMain()
, having executed the Runnable
parameter synchronously on the main UI thread.