Sunday, December 05, 2010

Loopers, MessageQueues, and Handlers (Part1)

Loopers, MessageQueues, and Handlers provide one of the facilities for processes and threads to communicate with each other. They are also one of the basis to implement the Services of Android framework. The relationship between these classes and how they interact with each other are not so easy to be seen through at the first glance. And that's why there are so many articles talking about them. This post does not intend to show they are used, but focus on the relationship between them.

To be short, their relationship could be described as below:
"A thread can be associated with at most one MessageQueue along with a Looper, which dispatches the Messages to the Handler."
Let's begin with the example code documented in the Looper.java source code.

frameworks/base/core/java/android/os/Looper.java

/** * Class used to run a message loop for a thread. Threads by default do * not have a message loop associated with them; to create one, call * {@link #prepare} in the thread that is to run the loop, and then * {@link #loop} to have it process messages until the loop is stopped. * * Most interaction with a message loop is through the * {@link Handler} class. * * This is a typical example of the implementation of a Looper thread, * using the separation of {@link #prepare} and {@link #loop} to create an * initial Handler to communicate with the Looper. * * 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(); * } * } */
As aforementioned, each thread can be associated *at most one* Looper and each Looper has exactly one MessageQueue. The Looper is implemented with the Singleton pattern, which is so extensively used in the Android framework. Making the constructor private prevents users from instantiating a Looper object directly with the new operator. Instead, they have to explicitly call the static method - Looper.prepare() to initialize one and associate it with the calling thread.
frameworks/base/core/java/android/os/Looper.java

/** Initialize the current thread as a looper. * This gives you a chance to create handlers that then reference * this looper, before actually starting the loop. Be sure to call * {@link #loop()} after calling this method, and end it by calling * {@link #quit()}. */ public static final void prepare() { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper()); } ... private Looper() { mQueue = new MessageQueue(); mRun = true; mThread = Thread.currentThread(); }
Once this is done, the thread is ready to accept and handle messages. Skipping the Handler part in the example code and continue to look into the implementation of Looper.loop(), we can see a simple forever loop which does the de-queuing, execution, and recycling of the messages from the associated MessageQueue.
The execution of a message is actually delegated to the dispatching() method of the "target" object associated. The target object is an instance of the Handler class or one of its subclasses.
frameworks/base/core/java/android/os/Looper.java

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(); } } }
Back to the example code, we can see the mHandler override its handleMessage() method to process the messages with it's intention.
frameworks/base/core/java/android/os/Looper.java

* mHandler = new Handler() { * public void handleMessage(Message msg) { * // process incoming messages here * } * };
The associations between the Handler instance and the Looper/MessageQueue are established during Handler() constructor. Unlike the MessageQueues/Loopers which are one-to-one associated with threads, more than one Handler instance could be associated to a single thread.
frameworks/base/core/java/android/os/Handler.java

/** * Default constructor associates this handler with the queue for the * current thread. * * If there isn't one, this handler won't be able to receive messages. */ public Handler() { mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = null; }
frameworks/base/core/java/android/os/Looper.java

/** * Return the Looper object associated with the current thread. Returns * null if the calling thread is not associated with a Looper. */ public static final Looper myLooper() { return (Looper)sThreadLocal.get(); }
A family of en-queuing methods are available for attributing the scheduling timing preference with the en-queued message.
frameworks/base/core/java/android/os/Handler.java

/** * Pushes a message onto the end of the message queue after all pending messages * before the current time. It will be received in {@link #handleMessage}, * in the thread attached to this handler. * * @return Returns true if the message was successfully placed in to the * message queue. Returns false on failure, usually because the * looper processing the message queue is exiting. */ public final boolean sendMessage(Message msg) { return sendMessageDelayed(msg, 0); } public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } public boolean sendMessageAtTime(Message msg, long uptimeMillis) { boolean sent = false; MessageQueue queue = mQueue; msg.target = this; sent = queue.enqueueMessage(msg, uptimeMillis); return sent; } public final boolean sendMessageAtFrontOfQueue(Message msg) { boolean sent = false; MessageQueue queue = mQueue; msg.target = this; sent = queue.enqueueMessage(msg, 0); return sent; }

No comments: