Adhere to the original day, short and fast Android advanced series, please directly in the wechat public number search: Nanchen, direct attention and set as the star standard, wonderful can not be missed.
In my interview series of 2017, I wrote an article titled “Android Interviewing (5) : Exploring the Android Handler”. The article mainly discusses the principles of the Handler, and then briefly gives some conclusions. Unexpectedly, two years later, I started another version of the interview series of daily questions, and this time, just to explore through the source code of details that we might normally ignore.
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:
Handler
,Looper
,Message
和MessageQueue
How do they relate to each other?MessageQueue
What is the storage structure?- Why does the child thread have to call
Looper.prepare()
和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.
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main3)
// Request network
subThread.start()
}
override fun onDestroy(a) {
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: Message) {
super.handleMessage(msg)
// The main thread handles the logic, usually using weak references to hold the Activity instance to avoid memory leaks}}private class SubThread(val handler: Handler) : Thread() {
override fun run(a) {
super.run()
// Time consuming operations such as network requests
// When the network request is complete, we need to notify the UI refresh
// The first method, usually this data is the content of the request result parsing
handler.obtainMessage(1.data).sendToTarget()
// The second method
val message = Message.obtain() // Initialize as much as possible using message.obtain ()
message.what = 1
message.obj = data // Usually this data is the content of the request result resolution
handler.sendMessage(message)
// The third way
handler.post(object : Thread() {
override fun run(a) {
super.run()
// Process the update operation}}}})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);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
Copy the code
The above code appears a MessageQueue and eventually calls the MessageQueue#enqueueMessage method to enqueue the message, so we have to briefly describe the basic situation 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.
boolean enqueueMessage(Message msg, long when) {
/ /... Omit some checking code
synchronized (this) {
/ /... Omit some 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(a) {
// ...
int nextPollTimeoutMillis = 0;
for (;;) {
// ...
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 (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 again for a 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:
- Less than 0, blocks until awakened;
- Equals zero, does not block;
- Greater than 0, longest block
nextPollTimeoutMillis
Milliseconds, which will be returned immediately if awakened.
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(a) {
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 the message to handler
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(a) {
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(a) {
Boolean bool = local.get();
Logger.i(SubThread reads: + bool);
// Set the value to 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:
MainThread reads the following values:trueThe SubThread reads the following values:true
Copy the code
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(a) {
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(a) {
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