Reprint a post I posted on CSDN.
Two questions I asked myself
Before we discuss Handler, Looper, and MessageQueue, we need to ask two questions:
-
What problem was this set up to solve?
-
What if we were to solve this problem?
The last two questions, which I recently summarized, are best answered before you learn about a new technology so that you can gain a deeper understanding of what you are learning.
First question: What problem did Google’s programmers come up with this thing to solve? This problem is obvious in order to solve the problem of communication between threads. We all know that Android’s UI/View system runs on the main thread, and the main thread is in an infinite loop, so let’s look at some concrete evidence.
public final class ActivityThread { public static void main(String[] args) { //... Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); }}Copy the code
As shown in the code example above, the ActivityThread.main() method is the entry point to the Android application, where I skip some initialization and then execute a looper.loop () method, which is gone, and throw the exception on the next line.
The loop() method is essentially an endless loop that keeps executing, fetching messages from an MQ (MessageQueue) and executing it or letting its sender process it, if any.
In general, the main thread loop is going to do some quick UI operations, and when you have a touch screen, the system will generate events, and the UI will handle those events, and those events will be executed in the main thread, and they will respond quickly to changes in the UI. If something time-consuming happens on the main thread, methods that follow it can’t be executed, and you get stuttering.
Therefore, Android doesn’t want you to do time-consuming operations on the main thread. The word “time-consuming” is simply understood as an operation that takes a lot of time to perform. For example, reading and writing files, small files may be fast, but you can’t predict the size of the file, or accessing the network, or you need to do some complicated calculations, etc.
Fluency in order not to block the main thread of execution, we must be in need of the time-consuming operation in other threads, and finished the work when other threads, and give a notice (with data) may well give to the main thread, the main thread to update the UI of what, of course, if you want to take operation is only the unknown finish line, does not need to notify the UI, So you don’t have to give notifications to the UI thread at all. So this is communication between threads, other threads doing time-consuming operations, telling the UI thread to update when it’s done. In order to solve this problem, The Android system provides us with such a set of solutions to solve.
Second question: If we were to come up with a solution to the problem of communication between threads, what would we do?
Let’s take a look at what we have now. We have a main thread that is always looping, which is implemented something like this:
public class OurSystem { public static void main(String [] args) { for (;;) { //do something... }}}Copy the code
Why does the main thread execute in an infinite loop?
About this, I personally did not particularly thorough cognition, but my guess is that, for a system with a GUI/program, should have a cycle of the main thread, because the GUI program is certainly want to interact with people, that is to say, needs to wait for user input, such as touch screen, move the mouse, keyboard, These events must be a response/signal that the hardware layer receives and then passes up the encapsulation.
If we touch the screen, touch the mouse, and start a new thread to process changes on the UI, first of all, of course we can! The UI can be updated on any thread, but it doesn’t have to be updated on the main thread. However, the problem is much more complicated, if we quickly click the mouse 2 times, then two new threads are opened to execute, then the order of execution of the two threads? Two independent threads, we can not guarantee that the first start first execute.
So the first problem is the order of execution.
The second problem is synchronization, where several independent threads dealing with the same resource can be confusing and out of control. On the other hand, imposing synchronization locks on all operations can be inefficient.
In order to solve the problem of order, very easy to think of a solution is the event queue, all kinds of events into a first in the queue, then something will continuously obtained from the queue, so that the first event must be performed before the second event, thus ensure the order, if we put this “event” step on a thread to do, This also solves the problem of resource synchronization.
Therefore, for GUI programs there is a (main) thread that loops all the time, probably from this way.
It’s a very pure loop, and if we want to do something, we have to ask it to get something from a queue to do it, just like a printer. So let’s write another message queue class to hold messages. The message queue should look like this:
public class OurMessageQueue() { private LinkedList mQueue = new LinkedList(); Public void enQueue() {//... } public Message deQueue() {//... } public Boolean isEmpty() {//... }}Copy the code
Then our loop needs to be able to get messages from the message queue and do something about them:
Public class OurSystem {public static void main(String [] args) {// Initialize the message queue OurMessageQueue =... for (;;) { if (! mq.isEmpty()) { Message msg = mq.deQueue(); //do something... }}}}Copy the code
Now let’s pretend that we need to click on a button to download a super-large file, and when the file is downloaded, let the main thread display the size of the file.
First of all, press the button, the event should be the trigger to the main thread (specific how to I also is not yet clear, but should be first starting from the hardware, and then inserted into the message queue, the cycle of the main thread can get into), and then the main line Cheng Kaiqi a new asynchronous thread to download, notify the main thread to update again after the download is complete, the code looks like this:
// Imagine hardware devices... Public class OurDevice {// The hardware device may have a callback public void onClick() {// Get the same message queue first and insert what we want to do into the queue. Message msg = Message.newInstance("download a big file"); mq.enQueue(msg); }}Copy the code
Our main thread loop then gets the message:
Public class OurSystem {public static void main(String [] args) {// Initialize the message queue OurMessageQueue =... for (;;) { if (! mq.isEmpty()) { Message msg = mq.deQueue(); If (msg.isdownloadbigFile ()) {// Start a new Thread to download the file. New Thread(new Runnable() {void run() {// download a big file, may cast 1 min... / /... OurMessageQueue =... Mq. enQueue(message.newinstance ("finished download")); } }).start(); If (msg.isfilishedDownload ()) {// update UI! } } } } }Copy the code
Note that when the main thread loop gets the message, it displays the judgment classification of the message. Different messages should be treated differently. In the news that our access to download a file, opened up a new thread to execute, time-consuming operation from the main thread is to different execution flow, when completed, a new thread with the same message queue messages sent a notice the download is complete, the main thread loop, after get it can update the UI.
This is a simple cross-thread communication scheme that I can casually imagine.
The following points are worth noting:
-
The main thread is an infinite loop that fetches messages from the message queue.
-
We want to send a message to the main thread’s message queue, and we need some way to get the main thread’s message queue object
-
How should messages be structured?
Interthread communication scheme in Android
Looper
android.os.Looper from Grepcode
Android has a Looper object, which literally means loop, and Looper does exactly that.
Looper’s code is very simple, with just over 300 lines without comments. In the comments to the official documentation, it recommends that we use it as follows:
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(); }}Copy the code
Let’s take a look at what the prepare method does.
Looper.prepare()
public static void prepare() { prepare(true); } private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() ! = null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); }Copy the code
The prepare(Boolean) method contains a sThreadLocal variable that functions like a hash table. Its key is the current thread. It stores references to the current thread. It checks if the current thread has a Looper object. If it has a Looper object, it says “Only one Looper may be created per thread”. If not, a new Looper object is inserted into the hash table. It then calls the Looper constructor.
Looper constructor
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}Copy the code
A key line in the Looper constructor is that it creates a MessageQueue object and maintains the reference to MQ itself.
Now that the prepare() method is done, you need to call the static method loop() to start the loop.
Looper.loop()
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;
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
msg.target.dispatchMessage(msg);
//...
}
}Copy the code
The loop() method, I’ve left out some of the bits I don’t care about. The rest is pretty clear. We first call the static method myLooper() to get a Looper object.
public static Looper myLooper() {
return sThreadLocal.get();
}Copy the code
MyLooper () is also a static method, and is fetched directly from a ThreadLocal. This is similar to a hash table, but the key is the current thread, because it prepared () with a Looper. Final MessageQueue queue = me. MQueue; Get with the corresponding MQ, which got the MQ, open the infinite loop, to keep the Message queue, when access to a Message, it calls the Message. The target. The dispatchMessage () method to the Message processing.
After looking at Looper’s code, we get a few things:
-
Looper calls prepare(), a static method, to initialize. Each thread can create only one Looper, and a Looper creates one MQ. You could say the three of them are on the same line.
-
Looper calls the static method loop() to start an infinite loop of fetch messages, and MQ calls the next() method to fetch messages
MessageQueue
android.os.MessageQueue from Grepcode
For the source of MQ, a quick look at the constructor and the next() method is enough.
MQ constructor
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}Copy the code
MQ’s constructor simply calls nativeInit() to initialize, which is a JNI method, that is, the object that may hold its message queue at the JNI layer.
MessageQueue.next()
Message next() { final long ptr = mPtr; if (ptr == 0) { return null; } int nextPollTimeoutMillis = 0; for (;;) { if (nextPollTimeoutMillis ! = 0) { Binder.flushPendingCommands(); } nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { // Try to retrieve the next message. Return if found. final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg ! = null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous message in the queue. do { prevMsg = msg; msg = msg.next; } while (msg ! = null && ! msg.isAsynchronous()); } if (msg ! = null) { if (now < msg.when) { // Next message is not ready. Set a timeout to wake up when it is ready. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // Got a message. mBlocked = false; if (prevMsg ! = null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (false) Log.v("MessageQueue", "Returning message: " + msg); return msg; } } else { // No more messages. nextPollTimeoutMillis = -1; }}}}Copy the code
Notice that this method also has an infinite loop. The effect of this is that in Looper’s infinite loop, next() is called, and next() is also in an infinite loop. On the surface, the method blocks on that line in Looper’s infinite loop. Know that the next() method returns a Message object.
A quick look at the MQ code reveals this:
-
Initialization of MQ is left to JNI
-
MQ’s next() method is an endless loop that accesses MQ and retrieves messages from it and returns them to Looper for processing.
Message
android.os.Message from Grepcode
A Message object is an element of a queue in MQ and an object that the Handler sends and receives for processing. For it, we need to know a few of its member attributes.
The member variable of Message can be divided into three parts:
-
Data part: It includes what(int), arg1(int), arg2(int), obj(Object), data(Bundle), etc. These are commonly used to pass data.
-
Sender (Target) : It has a member variable called target. This member variable is of type Handler. This member variable is important because it marks who sent the Message object itself and who will eventually handle it.
-
Callback: It has a member variable called callback, which is of type Runnable and can be interpreted as a code fragment that can be executed.
Handler
android.os.Handler from Grepcode
Handler object is the API level for developers to use the most of a class, we mainly through this class to send messages and processing messages.
Handler constructor (initialization)
Normally we call a constructor with no arguments to initialize it, which looks something like this:
Handler mHandler = new Handler() { handleMessage(Message msg) { //... }}Copy the code
The no-argument constructor ends up calling a two-argument constructor, part of which reads as follows:
public Handler(Callback callback, boolean async) {
//...
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 = callback;
mAsynchronous = async;
}Copy the code
Notice that it assigns a value to the mLooper member variable and obtains the Looper object corresponding to the current thread through the looper.mylooper () method. As mentioned above, if Looper calls prepare(), the thread corresponds to an instance of Looper, which corresponds to an INSTANCE of MQ, and there is a one-to-one correspondence between the three.
It then retrieves an MQ, via the mLooper object, in its own mQueue member variable.
The Handler initialization code indicates that the Handler initializes by assigning a reference to the thread’s Looper to the Handler that the Handler also holds
For the Main thread, in the Main thread’s execution flow, new a Handler object that holds the Main thread’s Looper object.
Similarly, if we start a Looper object in a new thread without calling looper.prepare (), it will report an error. Like this,
new Thread(new Runnable() { @Override public void run() { //Looper.prepare(); // Because Looper is not initialized, looper.mylooper () cannot get a Looper object Handler h = new Handler(); h.sendEmptyMessage(112); } }).start();Copy the code
The above code will run with an error:
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()Copy the code
Summary: Initialization of the Handler will fetch the Looper object of the current thread and the corresponding MQ object via Looper. If looper.prepare () has not been executed by the current thread, the Handler object cannot be created
Handler.sendMessage()
SendMessage messages have various forms or overloads that eventually call this method:
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}Copy the code
It calls the enqueueMessage method again:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}Copy the code
Notice that it assigns a value to the Target property of Message so that the Message knows from whom it was sent. The message is then added to the queue.
Handler.dispatchMessage()
Once the Message object enters MQ, it is quickly retrieved by MQ’s Next () method, which gives Looper a Message object in an endless loop. To recap, next, Call a Message. Target. DispatchMessage () method to handle the Message.
public void dispatchMessage(Message msg) { if (msg.callback ! = null) { handleCallback(msg); } else { if (mCallback ! = null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } } private static void handleCallback(Message message) { message.callback.run(); } public void handleMessage(Message MSG) {public void handleMessage(Message MSG) {Copy the code
The dispatchMessage method has two branches. If callback (Runnable) is not null, callback.run() is executed. If callback is null, MSG is passed to handleMessage(). So that’s the way we deal with it.
Message. The target and the Handler
In particular, notice the target member variable in Message, which points to its own sender. What does that mean?
This means that a thread with Looper can have many handlers, all of which are different objects, but all of which can send Message objects to the same MQ, where Looper continuously retrives messages from MQ and sends them to their senders for processing. An MQ can correspond to multiple handlers (multiple handlers can queue messages to the same MQ)
The following diagram briefly summarizes the relationship between them.
conclusion
-
Looper calls prepare() to initialize, creates a Looper object corresponding to the current thread (implemented via ThreadLocal), and initializes a MessageQueue object corresponding to the current Looper.
-
Looper calls the static method loop() to start the Message loop, retrieving the Message object through the messagequeue.next () method.
-
When a Message object is retrieved, let the target of the Message handle it.
-
A Message object consists of data, a sender (Handler), and an executable code segment (Runnable).
-
Handler can be initialized in a thread that has already initialized looper.prepare (). If the thread has not initialized Looper, creating the Handler object will fail
-
Multiple Handler objects can be constructed in the execution flow of a thread, all of which send messages to the same MQ, and the messages are only distributed to the corresponding Handler for processing.
-
The Handler sends the Message to MQ, the Target field of the Message references its sender, and the Looper is taken out of MQ and handed over to the Handler that sent the Message.
-
Message can be added directly to a Runnable object, and when the Message is processed, the runnable.run () method is executed directly.