Android has many background processing going on, however it is not very clear at first start how it works. The most general way of doing background tasks and preserving the UI thread is by using android.os.Handler instances. So what is this Handler all about!?
First of all we need to talk about the android.os.Looper class. It is literally a looper, which means that it loops inside a thread run() method waiting for messages to be posted at its message queue in order to be dispatched. Every Looper instance is composed of an andorid.os.MessageQueue instance that queues android.os.Message objects.
What links the Looper and the Handler classes together is the MessageQueue. Every handler is tightly related to a looper instance, dispatching all messages sent to it into the looper’s queue. As soon as the looper consumes the message from the queue, it will dispatch it to the message’s target handler. Note that every looper runs inside one thread and each thread can only have one associated looper, so all you need to do in order for handlers to work correctly is to make sure your handler is tightened to a looper running on the right thread instance.
See the Looper.loop(); method code bellow:
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static final void loop() {
Looper me = myLooper();
MessageQueue queue = me.mQueue;
while (true) {
Message msg = queue.next(); // might block
if (msg != null) {
if (msg.target == null) {
// No target is a magic identifier for the quit message.
return;
}
msg.target.dispatchMessage(msg);
msg.recycle();
}
}
}
So in order to create a Looper and attach it to a Thread instance see code bellow:
public void run() {
Looper.prepare();
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
...
}
};
Looper.loop();
}
The prepare method creates a Looper and associates it to the current running Thread. This is accomplished by using a java.lang.ThreadLocal field, which stores different Looper instances for each thread as shown bellow:
public static final void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper());
}
Wait… When was the Handler instance attached to the Looper in the code above ??? The Handler object was not passed anywhere! Well, the magic happens inside the Handler class’ default constructor as shown bellow:
public Handler() {
// Gets the looper associated to the current thread
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
// Gets the looper's queue in order to enqeue the
// dispatched messages in. See sendMessage methods.
mQueue = mLooper.mQueue;
mCallback = null;
}
Now we have a Handler associated to the current thread’s looper message queue. Every sendMessage method will enqueue the message object and the looper will consume the enqueued message on its associated thread. See one of the many sendMessage method flavors bellow:
public boolean sendMessageAtTime(Message msg, long uptimeMillis)
{
boolean sent = false;
MessageQueue queue = mQueue;
if (queue != null) {
msg.target = this;
sent = queue.enqueueMessage(msg, uptimeMillis);
}
else {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
}
return sent;
}
Are you on the Loop now? I hope so!!!