Today I read some blogs and source code about how Handler & Looper work together.
Based on what I've learnt, we can have only one Looper on each thread by using the ThreadLocal
magic. Usually Handler is initiated in main thread, or else you must manually start or saying, prepare
the Looper on a separate thread and then loop it up.
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();
}
}
What really confused me was the loop()
in main thread. As I read this in the source code of Looper. It's an endless loop to handle the message queue and then dispatch messages for callbacks to handle.
According to this https://stackoverflow.com/a/5193981/2290191, Handler and it's Looper run in the same thread.
If there is an endless loop on the main thread, wouldn't it block the entire UI system?
I know that I must be so silly to miss something. But it would be lovely for someone to reveal the secret behind this.
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked();
}
}
Android handles all the UI operations and input events from one single thread which is known as called the Main or UI thread. Android collects all events in this thread in a queue and processes this queue with an instance of the Looper class.
Looper drains the MessageQueue. Handler is created for some particular Looper and is used to post messages to it and handle these messages when they are processed. The relations are the following: Each Looper has exactly 1 MessageQueue.
Creating Looper and MessageQueue for a Thread: prepare() identifies the calling thread, creates a Looper and MessageQueue object and associate the thread with them in ThreadLocal storage class. Looper. loop() must be called to start the associated looper.
Looper is a class which is used to execute the Messages(Runnables) in a queue. Normal threads have no such queue, e.g. simple thread does not have any queue. It executes once and after method execution finishes, the thread will not run another Message(Runnable).
Actually the Looper in the main thread is what allows drawing. When a view is invalidated, a message is passed to the main Looper telling it that a draw was requested. When the Looper processes that message, the actual drawing occurs. The reason other activity that holds up the UI thread holds up drawing is that it prevents the Looper from processing that draw message.
This is more or less how drawing works in any event based system, from Windows to Mac to Android.
Why not draw immediately instead of sending a message? Performance. Drawing is slow. If you do multiple changes in response to an event, you don't want to redraw the screen for each one. Doing it this way means you bunch all of your redraws for handling a single event into 1 redraw. For example if you set the text of 1 view and the image of another, they'll both be redrawn at the same time, and only once.
This question is a delicate trap. Why infinite loops do not block UI threads because all UI threads behavior are starting from msg.next.
If there is no message, it means that no updates are required. All of our code is just a callback, such as Application onCreate, Activit onCreate, BroadcastReceiver onReceive.
All update callbacks are caused by the message, and these messages are from the system services, such as ActivityManagerService, InputManagerService, WindowMangerService. If you need to update the UI, the android service will send a message to the loop via the IPC.
So the infinite loop is infinite update.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With