We in the daily development, always will inevitably use Handler, although the Handler mechanism is not the same as the Android message mechanism, but the Handler message mechanism in Android development has long been familiar with the heart, very important!

Through this article, you can easily get answers to the following questions:

  1. How do Handler, Looper, Message, and MessageQueue work and how do they relate to each other?
  2. What is the MessageQueue storage structure?
  3. Why must the child thread call looper.prepare () and looper.loop ()?

Simple use of Handler

There’s no one who doesn’t use Handler. Let’s say you’re dealing with a time-consuming task in your Activity and you need to update your UI.

Super.oncreate (savedInstanceState) setContentView(r.layout.activity_main3) // Request network subthread.start ()} Override fun onDestroy() { subThread.interrupt() super.onDestroy() } private val handler by lazy(LazyThreadSafetyMode.NONE) { MyHandler() } private val subThread by lazy(LazyThreadSafetyMode.NONE) { SubThread(handler) } private class MyHandler : Handler() { override fun handleMessage(msg: Super.handlemessage (MSG) {super.handleMessage(MSG) {super.handleMessage(MSG); }} private class SubThread(val handler: handler) : Thread() {override fun run() {super.run() {override fun run() {super.run(); Other options are off the table for the time being. Handler. ObtainMessage (1,data).sendtotarget () val message = message.obtain ( Message.obtain() initialize message.what = 1 message.obj = data handler. SendMessage (Message Handler.post (object: Thread() {override fun run() {super.run()}})}}Copy the code

The above code is very simple, because the network request is a time-consuming task, so we start a new thread, and after the network request is finished and resolved, we notify the main thread to update the UI through the Handler. We simply adopt three methods, careful friends may find that the first method and the second method are the same. Handler sends a Message object with its contents. Message initialization should be done using message.obtain () instead of New Message() whenever possible, primarily to reduce memory footprint.

As suggested in the previous article, we tried to post as little source code as possible. You can easily find that all of the above methods will 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);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
returnqueue.enqueueMessage(msg, uptimeMillis); } </pre> The above code appears a MessageQueue, and eventually calls MessageQueueThe #enqueueMessage method is used to enqueue messages, so we have to go over the basics of MessageQueue.

# **MessageQueue**As the name implies, a MessageQueue is a Message queue, that is, a container for storing multiple messages. It uses a one-way linked list data structure instead of a queue. Its next() points to the next Message element in the list. <pre class="ql-align-justify" style="margin: 0px; padding: 15px; vertical-align: baseline; border: 0px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-numeric: inherit; font-variant-east-asian: inherit; font-weight: 400; font-stretch: inherit; line-height: inherit; font-family: monospace; font-size: 18px; white-space: pre-line; word-break: break-all; background-color: rgb(244, 245, 246); color: rgb(34, 34, 34); letter-spacing: normal; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">boolean enqueueMessage(Message msg, long when) { // ... Synchronized (this) {//... Omit some of the checking code 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 {
// Inserted within the middle of the queue. Usually we don't have to wake // up the event queue unless there is a barrier at the head of the queue // and the message is the earliest asynchronous message in the queue. needWake = mBlocked && p.target == null && msg.isAsynchronous(); 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

EnqueueMessage () is the next queuemessage () message, and the next queuemessage () message is the next queuemessage () message.

Message next() {/ /... int nextPollTimeoutMillis = 0;for(;;) {/ /... nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { // Try to retrieve the next message. Returnif 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 messagein 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 (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
returnmsg; }}else{ // No more messages. nextPollTimeoutMillis = -1; } / /... } / /... // While calling an idle handler, a new message could have been delivered // so go back and look againfora pending message without waiting. nextPollTimeoutMillis = 0; }}Copy the code

The next() method is actually quite long, but we’ve only pasted a very small part of it. As you can see, there’s just a for (;) inside. The body of the loop calls an nativePollOnce(long, int) method. This is a Native method that blocks the current call stack thread nextPollTimeoutMillis for milliseconds through the Native layer MessageQueue.

Here is how the nextPollTimeoutMillis values block in different cases:

  1. Less than 0, blocks until awakened;
  2. Equals zero, does not block;
  3. If the value is greater than 0, nextPollTimeoutMillis will be blocked for a maximum of milliseconds.

NextPollTimeoutMillis is initialized to 0, so it will not block and fetch Message directly. If it does not fetch Message data, it will set nextPollTimeoutMillis to -1. At this point, if the condition less than 0 is met, it will be blocked until another Native method nativeWake(Long) is called elsewhere to wake up. If the value is reached, the Message object is returned directly.

It turns out that the nativeWake(long) method has a call to the earlier MessageQueue#enqueueMessage method during the MessageQueue enqueueMessage.

You now know that the Handler sends the Message, stores the Message using the MessageQueue method, enqueueMessage method for queuing, and MessageQueue#next method for training the Message. This raises the question, who called the MessageQueue#next method? Yes, Looper.

Looper

Looper acts as a message loop in Android’s messaging mechanism, which means that it constantly checks for new messages from MessageQueue via Next () and processes them immediately, otherwise MessageQueue blocks.

Let’s look directly at Looper’s most important method: 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.");
}
// ...
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return; } / /... Try {/ / distribute message handler to process MSG. Target. DispatchMessage (MSG); dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0; } finally { // ... } / /... }}Copy the code

The method saves a lot of code and preserves only the core logic. As you can see, the myLooper() method first gets the Looper object, and if the Looper returns empty, the exception is thrown. Otherwise enter a for (;;). In the loop, the MessageQueue#next() method is rotated to fetch the Message object. If the Message object is empty, the loop() method is directly exited. Otherwise, just grab the Handler object via msg.target and call the Handler#dispatchMessage() method.

Let’s first look at the Handler#dispatchMessage() method implementation:

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();
}
Copy the code

Call message.callback.run() if Message sets callback, or if m is initialized

Now look at the myLooper() method:

public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
Copy the code

See what sThreadLocal is:

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
Copy the code

What is this ThreadLocal?

ThreadLocal

For ThreadLocal, let’s go straight to Yan zhenjie’s article.

The first impression of a ThreadLocal is that the class is thread-specific, which it is, but it is important to note that it is not a thread, otherwise it would be called a LocalThread.

ThreadnLocal is used to store data from a specified thread. When data is scoped by the specified thread and needs to be used throughout the execution of the thread, ThreadnLocal can be used to store data from the specified thread. Only this thread can read the stored data. No other thread can read the stored data.

If ThreadLocal is a ThreadLocal, it’s a ThreadLocal.

ThreadLocal<Boolean> local= new ThreadLocal<>(); // Set the initial value to true. Local.set (true);
Boolean bool = local.get();
Logger.i("MainThread reads:" + bool);
new Thread() {
@Override
public void run() {
Boolean bool = local.get();
Logger.i(SubThread reads:+ bool); // Set false. Local.set (false); }}.start(): // The main thread sleeps for 1 second before executing the code below. Thread.sleep(1000); Boolean newBool = local.get(); Logger.i("MainThread reads the new value as:" + newBool);
Copy the code

There’s nothing left to say about the code. The printed log will look something like this:

The MainThread reads the following values: trueSubThread reads the following values: True nullMainThread reads the following values: True

The first Log is unquestionable because the value is set to true, because there is nothing to print. For the second Log, as described above, the data stored by a thread using ThreadLocal can only be read by the thread, so the result of the second Log is null. ThreadLocal is set to false in the child thread, and the third Log will be printed. Similarly, the child ThreadLocal does not affect the main thread, so printing is true.

The experimental results confirm that the set() and GET () operations of any thread on a ThreadLocal object are independent of each other.

Looper.myLooper()

Let’s go back to looper.mylooper () :

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
Copy the code

Let’s see where sThreadLocal is operated on.

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

So you see, this is why looper.prepare () must be called before Handler is used in the child thread.

You may wonder if I didn’t ask for looper.prepare () when USING the main thread.

It turns out that in ActivityThread, we have to display a call to looper.prepareMainLooper () :

public static void main(String[] args) {
// ...
Looper.prepareMainLooper();
// ...
if(sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } / /... Looper.loop(); / /... }Copy the code

Let’s look at looper.prepareMainLooper () :

public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if(sMainLooper ! = null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); }}Copy the code

For this article, see: Exploring the Art of Android Development: Android messaging mechanisms and applications

The last

You and I are just a small part of a long road to Android development… Only continuous learning, progress, is our way out! To keep up with the progress of The Times!

If you see this and you think it’s good, give it a thumbs up? If you think there is something worth improving, please leave a message. Will inquire seriously, correct inadequacy. thank you

I hope you can forward share and follow me, I will update the technical dry goods in the future, thank you for your support!

Forward + like + attention, the first time to get the latest knowledge

The road to being an Android architect is a long one.