Handler mechanism is the knowledge point that the interviewer likes to ask very much. After reading the source code several times, I still feel unclear. The code inside is very convoluted. Later, I decided to give up exploring details and first draw a sketch of related classes and called methods, then clarify the relationship between calls, and then combine the high-frequency interview questions about Handler to find the answer. In this way, I will have a deeper understanding of Handler. Now put the process, the answers to the interview questions and the relevant explanations into this article, if there is any wrong, welcome to correct.
Frequent Interview questions
1. What are the ways to get Message instances? Which is better?
There are two main methods to obtain a Message instance: one is to create it directly, Message MSG = new Message. The other is to obtain a Message object through message.obtain () or handler.obtatinMessage (). The latter approach is preferred, in which objects are retrieved from the object collection pool and reused already processed Message objects rather than regenerating a new one. Message.obtain() and handler.obtatinMessage () both end up calling the Obtain () method of the Message class and looking at the contents of the method to obtain the Message from the object collection pool.
public static Message obtain() {
synchronized (sPoolSync) {
if(sPool ! = null) { Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; // clearin-use flag
sPoolSize--;
returnm; }}return new Message();
}
Copy the code
2. Do Message messages get cluttered when an Activity has multiple handlers? How do you tell which Handler is handling the current message?
There is no confusion, which Handler sends the message, and that Handler will also process it. When sending a message, the target is bound to the Handler itself. When the Handler calls dispatchMessage(MSG) to process the message, this Handler is bound to the Handler when sending the message. Whichever method is used to send a Message, enqueueMessage(MessageQueue Queue, Message MSG, Long uptimeMillis) is eventually called to send the Message
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
This, in this case, is the current handler. To see which Handler is used when a message needs to be processed by a Handler, the main source code is posted below.
public static void loop() {...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
......
if(traceTag ! = 0 && Trace.isTagEnabled(traceTag)) { Trace.traceBegin(traceTag, msg.target.getTraceName(msg)); } final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0; final long dispatchEnd; try { msg.target.dispatchMessage(msg); dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0; } finally {if(traceTag ! = 0) { Trace.traceEnd(traceTag); }}... msg.recycleUnchecked(); }}Copy the code
This is part of the cycle message code, and handle the message code is MSG. The target. The dispatchMessage (MSG); Where target is the handler that sent the message.
3. Sending messages in the child thread but receiving messages in the main thread, how to switch the main thread and child thread?
The child thread sends the message to the handler, and the message is sent to the MessageQueue associated with the main thread. The Looper associated with the main thread circulates the message, so the final message processing logic is also in the main thread. The Handler is associated with the Looper of which thread. The message processing logic is executed in the thread related to it, and the direction of the corresponding message is also in the associated MessageQueue. So the child thread switching to the main thread is a natural process, not as complicated as you might think.
4. Can I create a Handler in a child thread?
Yes, but Looper is created by calling prepare() before creation. When created, the Handler checks to see if a Looper has been created and throws an exception if it has not. The relevant source code is as follows:
public Handler(Callback callback, boolean async) {
......
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
Copy the code
So we need to call prepare() on the child thread to create Looper. There is no need to create a Looper for the main thread, because the ActivityTread class already creates a Looper for us.
public static void main(String[] args) { ...... Looper.prepareMainLooper(); // Create looper for the main thread // Find the valuefor {@link #PROC_START_SEQ_IDENT} if provided on the command line.
// It will be in the format "seq=114"
long startSeq = 0;
if(args ! = null) {for (int i = args.length - 1; i >= 0; --i) {
if(args[i] ! = null && args[i].startsWith(PROC_START_SEQ_IDENT)) { startSeq = Long.parseLong( args[i].substring(PROC_START_SEQ_IDENT.length())); } } } ActivityThread thread = new ActivityThread(); thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
Copy the code
Looper.prepareMainLooper(); This code creates a Looper for the main thread. So creating a Handler in the main thread will loop messages directly, because Android’s main thread already has Looper for us.
5. How many handlers can a thread have? A few stars?
A thread can have multiple handlers, but only one Looper. Before creating Handler, you need to create Looper; otherwise, an error will be reported. The source code has been explained.
public Handler(Callback callback, boolean async) {
......
mLooper = Looper.myLooper();
if(mLooper == null) {throw new RuntimeException("Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
Copy the code
Again, Looper is created in the prepare() method.
// sThreadLocal.get() will return null unless you've called prepare(). static final ThreadLocal
sThreadLocal = new ThreadLocal
(); 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
If a looper exists, Only one Looper may be created per Thread. If a looper exists, Only one Looper may be created per thread. TreadLocal is used for isolation between threads, ensuring that one thread corresponds to one Looper. You can also look at the source code for the Looper constructor
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Copy the code
MessageQueue is initialized in the Looper constructor. No matter how many handlers a thread has, they are all associated with the same Looper and MessageQueue.
There are a lot of questions you can ask about handlers, but these are just a few that I think are important. After looking for answers, I went through the whole process of the Handler mechanism in my mind and made a sketch.
An interrelation diagram of important classes in the Handler mechanism
final Looper mLooper;
final MessageQueue mQueue;
public Handler(Callback callback, boolean async) {
......
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
Copy the code
mLooper = Looper.myLooper(); So the Looper is the main thread Looper mQueue = mlooper.mqueue; The resulting MessageQueue is the MessageQueue created in the Looper constructor. Once the Handler instance is created, we call handler.sendMessage(MSG) on the child thread; Send the message and put the message in the MessageQueue. In enqueueMessage (), each message is assigned a target, which is the current handler.
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
Then call looper.loop (); (in ActivityThread main () has helped us to write good) began circulating messages, to get the message out later will use a handler for processing, key code is MSG. Target. DispatchMessage (MSG); This handler corresponds to the handler we set for Message in the first place.
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if(msg.callback ! = null) { handleCallback(msg); }else {
if(mCallback ! = null) {if (mCallback.handleMessage(msg)) {
return; } } handleMessage(msg); }}Copy the code
Finally we process our message inside the Handler’s handleMessage(MSG). This is the whole process of a child thread sending a message to the main thread, there is a lot of source code, I just captured part of the focus. One more thing to mention here is that threads handle messages. There are three things you need to do to process a message in a thread
Create a Looper (looper.prepare ()). Create a Handler that is associated with the current thread’s Looper by default. Loop message (looper.loop ())
The order of these three steps cannot be reversed. Since the main thread has already created the Looper for us, we don’t need to write it, as we would if we created the Looper on the child thread.
Handler mechanism understanding to rely on their own to consider, constantly look at the source code, to understand, clarify the mutual call between them. Only with a deep understanding of Handler will you be able to answer the interviewer’s questions during the interview. Rote learning is not recommended. Take the time to watch, use your brain, don’t slack off, and always get Handler down.