We often use Handler, which is the android class for thread switching. We just need to send a message to any thread and process the received message in the handleMessage method to complete the thread switching. Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler This article will give you answers to all of the above questions, as well as a thorough understanding of the Handler messaging mechanism. Most importantly, you will be able to let the interviewer know how well you understand Handler to raise your salary and raise your position.
There are three important things in the Handler messaging mechanism: Handler, MessageQueue, and Looper. Let’s take a look at these three things in turn:
When we use a Handler, we create a new Handler and override the handleMessage method. Some people like to pass a CallBack to the Handler and implement the handleMessage method in the CallBack. It’s the same thing. The analysis will cover that later. Let’s look at the construction first, and see how the Handler is created.
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
Both of the above methods will end up calling the above construct. As you can see, there is some assignment, but one value Looper must not be null. Let’s see how Looper is obtained from looper.mylooper ().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); public static @Nullable Looper myLooper() { return sThreadLocal.get(); } public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map ! = null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e ! = null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }Copy the code
Threadlocalmap. Entry (value) is the Looper of the current thread. We’ve seen that we need a Looper to create a Handler. How is this Looper created? Public static void Prepare () creates a Looper.
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
A thread can only have one Looper. If the current thread does not have a Looper, the thread can be created. And put the Looper set inside the sThreadLocal. Let’s look at the set method
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map ! = null) map.set(this, value); else createMap(t, value); }Copy the code
Very simply, you set the ThreadLocal object as the key and the Looper object as the value
The logic is a little convoluted, let’s rearrange it: A Thread has a ThreadLocalMap and a Looper has a static ThreadLocal. The ThreadLocalMap contains an Entry with a ThreadLocal key and a Looper value. When you call looper.mylooper () from a Thread to retrieve the current Thread’s Looper, you will use the sThreadLocal as the key to retrieve the Looper from the current Thread’s ThreadLocalMap. The process looks for Looper in the current thread each time it is created and throws an exception if there is one. So this ensures that Looper is unique within a single thread.
We’re looking at the creation of Looper
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Copy the code
MessageQueue is created and the current thread is assigned to the mThread member variable.
Now that we know about Handler creation and Looper’s guarantee of single-thread uniqueness, we look at our most common sendMessage and POST methods, both of which eventually call sendMessageAtTime
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) {
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);
}
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
The sendMessageDelayed() method is called, Systemclock. uptimeMillis()+delayMillis (); systemclock. uptimeMillis(); Changing the system time will not affect the execution of the delayed message of Handler, but the Timer delay is based on the system time as the standard, so we will encounter such a pit), You can see that after making a judgment call to set handler to the Message target, we call MessageQueue’s enqueueMessage method directly. Let’s look at the enqueueMessage method
boolean enqueueMessage(Message msg, long when) { if (msg.target == null) { throw new IllegalArgumentException("Message must have a target."); } if (msg.isInUse()) { throw new IllegalStateException(msg + " This message is already in use."); } synchronized (this) { if (mQuitting) { IllegalStateException e = new IllegalStateException( msg.target + " sending message to a Handler on a dead thread"); Log.w(TAG, e.getMessage(), e); msg.recycle(); return false; } msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; if (p == null || when == 0 || when < p.when) { // New head, wake up the event queue if blocked. msg.next = p; mMessages = msg; needWake = mBlocked; } else { Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg; } // We can assume mPtr ! = 0 because mQuitting is false. if (needWake) { nativeWake(mPtr); } } return true; }Copy the code
Is in front of the judge some abnormal situation, we look at the following if else statements directly, if p = = null judgment in | | the when = = 0 | | the when < p.w hen, you can see on a p In fact just mMessages, This mMessages is the next message to be processed, p==null (there is no next message to be processed, message queue has no message), when is the timestamp of the execution of the message we sent (when we send a delayed message, the current time is automatically added to get the timestamp of the message execution), When == 0 (to prove that no delay is needed), and when < p.hen (when we send the message before the next message to be executed), call MSG. Next = p (that is, put the next message to be executed after the message we sent, and the one we sent before). This is how messages are queued in several cases; We look again at the else inside (can be judged from the if condition, we send the message must be the next to perform message behind), prev = p p = p.n ext to continue just after the if (p = = null | | the when < p.w hen) (actually to traverse the message queue, See where the message we sent should be inserted), and then msg.next=p prev.next= MSG (insert our message after the location is determined), this is the logic of the entire message insertion queue.
Now that we know how the message is inserted, let’s look at how the message is pulled out and executed. The message is executed using Looper polling. Let’s look at Looper loop method
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; Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); for (;;) { Message msg = queue.next(); // might block if (msg == null) { return; } final Printer logging = me.mLogging; final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs; final long traceTag = me.mTraceTag; if (traceTag ! = 0 && Trace.isTagEnabled(traceTag)) { Trace.traceBegin(traceTag, msg.target.getTraceName(msg)); } final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis(); final long end; try { msg.target.dispatchMessage(msg); end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis(); } finally { if (traceTag ! = 0) { Trace.traceEnd(traceTag); } } if (slowDispatchThresholdMs > 0) { final long time = end - start; if (time > slowDispatchThresholdMs) { Slog.w(TAG, "Dispatch took " + time + "ms on " + Thread.currentThread().getName() + ", h=" + msg.target + " cb=" + msg.callback + " msg=" + msg.what); } } 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(); }}Copy the code
The key to this code is to loop through the queue and call Queue.next () to fetch messages from MessageQueue. If you need to understand the students can see understand MessageQueue this article), and through the MSG. Target. DispatchMessage (MSG) to perform the message, we know that the target is to send the message Handler. Moving on to the dispatchMessage method
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
Did you realize something? The first if executes the MSG callback, which is a Runnable object assigned by handler.post(Runnable Runnable), Else The if calls the handleMessage(MSG) method of the mCallback that we passed to the Handler when we created it, and finally calls the handleMessage(MSG) of the Hanler itself. So there are three ways that handlers can handle messages.
To create a Handler for the current thread, we need to create a Looper bound to the current thread. Looper.prepare() will do this. After looper.loop (), MessageQueue polls, the Handler system of the whole thread has been created, we only need to create the Handler to send the message, the last message processing code will be completed in the customized thread, so as to achieve the purpose of thread switch.
Instead of calling the prepare() and loop() methods of Looper, this is because the system calls the main method of ActivityThread
public static void main(String[] args) { SamplingProfilerIntegration.start(); CloseGuard.setEnabled(false); Environment.initForCurrentUser(); EventLogger.setReporter(new EventLoggingReporter()); Security.addProvider(new AndroidKeyStoreProvider()); final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId()); TrustedCertificateStore.setDefaultUserDirectory(configDir); Process.setArgV0("<pre-initialized>"); PrepareMainLooper (); prepareMainLooper(); ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } AsyncTask.init(); if (false) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); } // let Looper poll looper.loop (); throw new RuntimeException("Main thread loop unexpectedly exited"); }Copy the code
As you can see, the reason we use Hanlder directly on the main thread is that the system has completed the prepare and loop for the main thread when the application is started.
Handler handler handler handler handler handler handler handler handler handler handler handler handler handler handler handler handler