Handler is a very important part of the Android development system. It has a very clear purpose for developers to implement thread switches or perform delayed tasks. A slightly more advanced use of Handler may be to ensure that multiple tasks are executed in order.
Due to the special status of the main thread in Android, tripartite libraries such as EventBus and Retrofit, which are not unique to Android, use handlers to support Android’s particular platform. Most developers are already familiar with how to use Handler, so here’s a look at how it’s implemented internally.
First, to implement Handler
This article will not introduce the source code directly at the beginning, but will first be based on the effect we want to achieve the source code backwards, step by step to implement a simple handler.html
1, the Message
First, we need a vector to represent the task to be executed. Let’s call it Message. What parameters should Message take?
- We need to have a unique identifier, because there may be multiple tasks to perform, and we need to know which is which, and an Int is enough
- You need to be able to hold data. There are many different types of data you need to send, so you can use an Object variable to represent the data
- You need a variable of type LONG to represent the execution timestamp of the task
Therefore, the Message class should contain at least the following fields:
/** * @Author: leavesC * @Date: 2020/12/1 13:31 * @Desc: * GitHub:https://github.com/leavesC * / public final class Message {/ / unique identifier public int I; Public Object obj; // Timestamp public long when; }Copy the code
2, MessageQueue
Since messages are not sent and consumed immediately, there must be a place to store them. Call it a MessageQueue, or Message queue. Message may need to be delayed, so MessageQueue should store messages in sequence according to the size of time stamps when saving messages. Messages with small time stamps should be placed at the head of the queue, and values can be directly taken from the queue head when consuming messages
So what data structure is good for storing messages?
- Use an array? Not suitable. Arrays are faster to traverse, but require a fixed amount of memory in advance, which can result in large amounts of data being moved when inserting and removing data. MessageQueue may receive a variable number of messages and a variable timestamp size at any time, and need to remove the Message from the queue after consuming it, so using arrays is not appropriate
- Use the list? It seems possible. The linked list only needs to change the reference of pointer when inserting and removing data. There is no need to move data, and the memory space only needs to be allocated as needed. Although linked lists perform poorly on random access, it doesn’t matter for MessageQueue because messages are consumed only by fetching the value of the queue header, not by random access
Ok, now that you’ve decided to use the list structure, Message needs to add a field to point to the next Message
/** * @Author: leavesC * @Date: 2020/12/1 13:31 * @Desc: * GitHub:https://github.com/leavesC * / public final class Message {/ / unique identifier public int I; Public Object obj; // Timestamp public long when; Public Message next; }Copy the code
MessageQueue needs to provide an enqueueMessage method to insert messages to the linked list. Since it is possible for multiple threads to send messages to the queue at the same time, thread synchronization is required within the method
/** * @Author: leavesC * @Date: 2020/12/1 13:31 * @Desc: * GitHub:https://github.com/leavesC * / public class MessageQueue {/ / the first Message in the list private Message mMessages; void enqueueMessage(Message msg, long when) { synchronized (this) { Message p = mMessages; / / if the list is empty, or in a team head message timestamp is bigger than the MSG, if will MSG as list head (p = = null | | the when = = 0 | | the when < p.w hen) {MSG. Next = p; mMessages = msg; } else { Message prev; For (MSG); // Start the list from the header to the end of the list. ;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } } msg.next = p; prev.next = msg; }}}}Copy the code
In addition, MessageQueue needs to have a method to get the header message, called next(). It is possible to send messages to MessageQueue at any time, so the next() method starts an infinite loop inside to iterate. If the current header message can be processed directly (that is, the timestamp of the message is less than or equal to the current time), the header message is returned directly. If the timestamp of the queue header message is larger than the current time (that is, the queue header message is a delayed message), calculate the difference between the current time and the timestamp of the queue header message, calculate how long the next() method needs to wait for blocking, and call the nativePollOnce() method to wait for some time before continuing the loop
Private Boolean mBlocked = false; Message next() { int nextPollTimeoutMillis = 0; for (; ;) { nativePollOnce(nextPollTimeoutMillis); Synchronized (this) {// Final long now = systemclock. uptimeMillis(); synchronized (this) {final long now = systemclock. uptimeMillis(); Message msg = mMessages; if (msg ! = null) {if (now < MSG. When) {// If (now < MSG. NextPollTimeoutMillis = (int) math.min (msg.when - now, integer.max_value); } else {mMessages = msg.next; msg.next = null; mBlocked = false; return msg; } } else { // No more messages. nextPollTimeoutMillis = -1; } mBlocked = true; Private void nativePollOnce(long nextPollTimeoutMillis) {}Copy the code
Consider a situation where, while next() is still blocking, an external Message is inserted into the Message queue that can be processed immediately or has a short blocking wait. At this point, the dormant thread needs to be woken up, so enqueueMessage needs to add logic to determine whether the next() method needs to be woken up
Void enqueueMessage(Message MSG, long when) {synchronized (this) {needWake = false; Message p = mMessages; / / if the list is empty, or in a team head message timestamp is bigger than the MSG, if will MSG as list head (p = = null | | the when = = 0 | | the when < p.w hen) {MSG. Next = p; mMessages = msg; NeedWake = mBlocked; } else { Message prev; For (MSG); // Start the list from the header to the end of the list. ;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } } msg.next = p; prev.next = msg; } if (needWake) {// Wake up the next() method nativeWake(); Private void nativeWake() {}Copy the code
3, the Handler
Now that MessageQueue has been identified as the place to store messages, it’s natural to have a class that can send messages to MessageQueue, let’s call it Handler. What does Handler do?
- You want to be able to send Runnable messages in addition to messages of type Message. This is simple. The Handler internally wraps the Runnable as a Message
- You want to send delayed messages to perform delayed tasks. This is also simple, using the when field inside Message to identify the timestamp when you want the task to execute
- It is desirable to implement thread switching so that messages sent from child threads can be executed on the main thread and vice versa. A child thread can send a message to a particular mainMessageQueue, and then let the main thread loop to fetch the message from that queue and execute it.
So, Message definition and sending are handled by handlers, but Message distribution can be handed off to other threads
For Runnable to be wrapped as a Message, the processing logic for a Message needs to be defined by a Handler, so Message needs to add two more fields
/** * @Author: leavesC * @Date: 2020/12/1 13:31 * @Desc: * GitHub:https://github.com/leavesC * / public final class Message {/ / unique identifier public int I; Public Object obj; // Timestamp public long when; Public Message next; // Wrap Runnable as Message public Runnable callback; Public Handler target; // The sender of the Message and the final Handler of the Message. }Copy the code
Handler needs to contain at least a few methods: a method for sending messages and Runnable, a handleMessage method for processing messages, and a dispatchMessage method for distributing messages
/** * @Author: leavesC * @Date: 2020/12/1 13:31 * @Desc: * GitHub:https://github.com/leavesC * / public class Handler {private MessageQueue mQueue; public Handler(MessageQueue mQueue) { this.mQueue = mQueue; } public final void post(Runnable r) { sendMessageDelayed(getPostMessage(r), 0); } public final void postDelayed(Runnable r, long delayMillis) { sendMessageDelayed(getPostMessage(r), delayMillis); } public final void sendMessage(Message r) { sendMessageDelayed(r, 0); } public final void sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } public void sendMessageAtTime(Message msg, long uptimeMillis) { msg.target = this; mQueue.enqueueMessage(msg, uptimeMillis); } private static Message getPostMessage(Runnable r) { Message m = new Message(); m.callback = r; return m; } // Override the method externally, Public void handleMessage(Message MSG) {} public void dispatchMessage(Message MSG) {if (msg.callback ! = null) { msg.callback.run(); } else { handleMessage(msg); }}}Copy the code
The child thread can then use the Handler like this: Bind the Handler object held by the child thread to the mainMessageQueue associated with the main thread The main thread is responsible for looping the Message from the mainMessageQueue and then calling Handler’s dispatchMessage method to implement thread switching
Handler handler = new Handler(mainThreadMessageQueue) { @Override public void handleMessage(Message msg) { switch (msg.what) { case 1: { String ob = (String) msg.obj; break; } case 2: { List<String> ob = (List<String>) msg.obj; break; }}}}; Message messageA = new Message(); messageA.what = 1; messageA.obj = "https://github.com/leavesC"; Message messageB = new Message(); messageB.what = 2; messageB.obj = new ArrayList<String>(); handler.sendMessage(messageA); handler.sendMessage(messageB);Copy the code
4, which
Now let’s think about how the Handler gets the MessageQueue associated with the main thread and how the main thread gets the Message from the MessageQueue and calls back to the Handler. There must be a relay, let’s call it a Looper. What exactly does Looper need to do?
- Each Looper object should have a unique MessageQueue and Thread instance so that threads and main threads can send messages to each other for processing
- An infinite loop needs to be opened inside the Looper, and its associated thread is responsible for retrieving messages from the MessageQueue loop for processing
- Since the main thread is special, consider storing the Looper object associated with the main thread as a static variable if it is directly available to the quilt thread
So you have the big picture of Looper. Each thread initializes its own Looper instance by using the prepare() method. MyLooper () method is used to retrieve the Looper object associated with the current thread. The sMainLooper associated with the main thread exists as a static variable for child threads to fetch
/**
* @Author: leavesC
* @Date: 2020/12/1 13:31
* @Desc:
* GitHub:https://github.com/leavesC
*/
final class Looper {
final MessageQueue mQueue;
final Thread mThread;
private static Looper sMainLooper;
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private Looper() {
mQueue = new MessageQueue();
mThread = Thread.currentThread();
}
public static void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper());
}
public static void prepareMainLooper() {
prepare();
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
public static Looper myLooper() {
return sThreadLocal.get();
}
}
Copy the code
Looper also needs to have a method that loops to get messages from MessageQueue and process them, call it loop(). It can also be as simple as looping out the Message from the MessageQueue and then distributing the Message back to the Handler
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(); / / may block the MSG. Target. DispatchMessage (MSG); }}Copy the code
The main thread initializes the sMainLooper by calling prepareMainLooper(), and then calls loop() to loop in the mainMessageQueue for processing. Without a message, the main thread is temporarily dormant. The child thread takes the sMainLooper and initializes the Handler with it. In this way, messages sent by the thread to the Handler are stored in the mainMessageQueue and consumed by the main thread
5. Summarize
The Message, MessageQueue, Handler, and Looper classes should all be clear. Different threads can then rely on each other’s Looper for cross-thread processing of messages
For example, for the following code, the handleMessage method is eventually called on mainThread, even though the Handler is initialized on the otherThread.
Thread mainThread = new Thread() {@override public void run() {// Initialize mainLooper looper.prepareMainLooper (); // Open looper.loop (); }}; Thread otherThread = new Thread() { @Override public void run() { Looper mainLooper = Looper.getMainLooper(); Handler handler = new Handler(mainLooper.mQueue) { @Override public void handleMessage(Message msg) { switch (msg.what) { case 1: { String ob = (String) msg.obj; break; } case 2: { List<String> ob = (List<String>) msg.obj; break; }}}}; Message messageA = new Message(); messageA.what = 1; messageA.obj = "https://github.com/leavesC"; Message messageB = new Message(); messageB.what = 2; messageB.obj = new ArrayList<String>(); handler.sendMessage(messageA); handler.sendMessage(messageB); }};Copy the code
Here’s a quick summary:
- Message: Used to indicate the task to be executed
- Handler: If the Handler is bound to the main thread’s MessageQueue, the Message sent by the child thread can be consumed by the main thread for thread switching, UI updating, etc
- MessageQueue: Message queue. Messages sent by the Handler are not executed immediately. They need to be stored in MessageQueue according to Message priority (delay time), and then executed in sequence
- Stars: Looper is used to loop messages from the MessageQueue and pass them to the Message Handler (the Message sender Handler itself) for consumption. Each Message has a target variable that points to the Handler. This associates a Message with its handler. Different threads send messages across threads by taking each other’s Looper objects
With that in mind, let’s take a look at the actual source code implementation
Handler source code
1. How to initialize Handler
There are seven constructors for Handler, and apart from the two deprecated ones and the three hidden ones, there are actually only two developers can use. No matter which constructor is used, the ultimate purpose is to complete the initialization of the four constants mLooper, mQueue, mCallback and mAsynchronous. It can also be seen that MessageQueue is initialized by Looper. Handler has a one-to-one relationship with both Looper and MessageQueue, which cannot be changed once initialized
Most developers will probably use Handler’s no-argument constructor, which has been marked deprecated in Android 11. Google officials prefer to do this by explicitly passing in a Looper object rather than implicitly using the current thread-associated Looper
Handler has a one-to-one relationship with both Looper and MessageQueue, but Looper and MessageQueue can have a one-to-many relationship with Handler, which will be discussed later
@UnsupportedAppUsage final Looper mLooper; final MessageQueue mQueue; @UnsupportedAppUsage final Callback mCallback; final boolean mAsynchronous; /** * @hide */ public Handler(@nullable Callback Callback, boolean async) { if (FIND_POTENTIAL_LEAKS) { final Class<? extends Handler> klass = getClass(); if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) { Log.w(TAG, "The following Handler class should be static or leaks might occur: " + klass.getCanonicalName()); } } 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
2. How to initialize Looper
When the Handler is initialized, if no Looper is passed in by the externally called constructor, looper.myLooper () is called to retrieve the Looper object associated with the current thread, and MessageQueue is fetched from the Looper. If the Looper object is null, an exception is thrown. Can’t create handler inside thread that has not called Looper. Prepare () Looper is initialized by calling looper.prepare () before initializing the Handler
As you can see from the Looper class, myLooper() is a static method of the Looper class that simply takes the value from the sThreadLocal variable and returns it. SThreadLocal is initialized using the prepare(Boolean) method, and can only be assigned once. Repeated calls will throw an exception
As we know, ThreadLocal can maintain a single variable instance for different threads, so different threads can have a one-to-one relationship with different Looper objects
@UnsupportedAppUsage static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); /** * Return the Looper object associated with the current thread. Returns * null if the calling thread is not associated with a Looper. */ public static @Nullable Looper myLooper() { return sThreadLocal.get(); } /** Initialize the current thread as a looper. * This gives you a chance to create handlers that then reference * this looper, before actually starting the loop. Be sure to call * {@link #loop()} after calling this method, and end it by calling * {@link #quit()}. */ 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
In addition, the Looper constructor is private and initializes two constant values: mQueue and mThread. This means that Looper is a one-for-one correspondence between MessageQueue and Thread, and cannot be changed after association
@UnsupportedAppUsage
final MessageQueue mQueue;
final Thread mThread;
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Copy the code
In daily development, we often use Handler’s no-argument constructor to perform UI refresh operations, so we must use the Looper object associated with the main thread, corresponding to the static variable sMainLooper in the Looper class
@UnsupportedAppUsage private static Looper sMainLooper; // Guarded by looper. class // The system was rejected because sMainLooper would be automatically initialized by the Android system. @deprecated public static void prepareMainLooper() {prepare(false); synchronized (Looper.class) { if (sMainLooper ! = null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } } /** * Returns the application's main looper, which lives in the main thread of the application. */ public static Looper getMainLooper() { synchronized (Looper.class) { return sMainLooper; }}Copy the code
PrepareMainLooper () is used to initialize the Looper object for the main thread, which in turn is called by the Main () method of the ActivityThread class. The main() method is the starting point of the Java program, so when the application starts, the system automatically initializes the mainLooper in the main thread. The looper.loop () method has been called to enable the loop processing of messages, and various interaction logic in the application process (for example: Screen touches, list slides, etc.) are distributed in this loop
Since Android automatically initialises the main thread Looper, we can use Handler’s no-argument constructor directly to handle UI-related events in the main thread
Public Final Class ActivityThread extends ClientTransactionHandler {public static void main(String[] args) {··· · Looper.prepareMainLooper(); ... stars. The loop (); throw new RuntimeException("Main thread loop unexpectedly exited"); }}Copy the code
3. Handler sends the message
Handler has dozens of methods for sending messages, most of which end up calling the sendMessageAtTime() method. UptimeMillis is the specific timestamp that Message is going to execute. If this timestamp is larger than the current time, it means that a delayed task is going to execute. If the mQueue is null, the exception Message is printed and returned directly, since the Message needs to be handled by MessageQueue
public boolean sendMessageAtTime(@NonNull 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
MSG. Target = this; target refers to the object that sends the message. The message sent by the Handler object to MessageQueue is processed by the Handler object
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) { msg.target = this; msg.workSourceUid = ThreadLocalWorkSource.getUid(); if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(MSG, uptimeMillis); }Copy the code
4, MessageQueue
MessageQueue receives messages through the enqueueMessage method
- Since it is possible for multiple threads to send messages to a MessageQueue at the same time
enqueueMessage
There is definitely an internal need for thread synchronization - It can be seen that MessageQueue internally stores messages in a linked list structure (message.next), and its position in the Message queue is determined by the timestamp size of the Message
- MMessages represents the first message in the message queue. If mMessages is empty (the message queue is empty), or if the timestamp of mMessages is larger than the timestamp of the new message, the new message is inserted into the header of the message queue; If mMessages is not empty, look for the first non-empty message in the message queue whose trigger time is later than the new message and insert the new message in front of it
At this point, a message queue sorted by timestamp size is complete, and the next thing to do is pull messages out of the message queue for processing
boolean enqueueMessage(Message msg, long when) { if (msg.target == null) { throw new IllegalArgumentException("Message must have a target."); } synchronized (this) {··· msg.markinuse (); msg.when = when; Message p = mMessages; Boolean needWake; / / if the list is empty, or in a team head message timestamp is bigger than the MSG, will MSG as chain table head / / s = = 0 Handler is invoked sendMessageAtFrontOfQueue method, If directly put the MSG queue head (p = = null | | the when = = 0 | | the when < p.w hen) {/ / New head, wake up the event queue if blocked. msg.next = p; mMessages = msg; needWake = mBlocked; } else {needWake = mBlocked && p.target == null && if the current thread is sleeping + queue header message is barrier message + MSG is asynchronous message // then the thread needs to be awakened msg.isAsynchronous(); Message prev; For (;;); (MSG); (MSG); (MSG); { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake &&p.isasynchronous ()) {needWake = false; if (needWake &&p.isasynchronous ()) { } } 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
Now that you know how the Message is stored, let’s look at how MessageQueue fetches the Message and calls it back to Handler. Reading the message in MessageQueue corresponds to the next() method. The next() method internally opens an infinite Loop, which causes the Loop thread to hibernate and hang until the condition is met, if there are no messages in the queue or if the header message is not ready to be processed
@unsupportedappusage Message next() {··· for (;;) { if (nextPollTimeoutMillis ! = 0) { Binder.flushPendingCommands(); } // Suspend Loop thread sleep 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) { NextPollTimeoutMillis = (int) 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(); return msg; } } else { // No more messages. nextPollTimeoutMillis = -1; } ··· ·} ···}Copy the code
The next() method is looped through the loop() method of Looper, which is also an infinite loop and can only be broken out of if queue.next() returns null. Because the next() method might trigger a blocking operation, the loop() method would also block when there were no messages to process, and when MessageQueue had a new message, Which will handle the message in a timely manner and call the MSG. Target. DispatchMessage (MSG) method the message back to the Handler
/** * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the 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; }, MSG. Target. DispatchMessage (MSG); ...}}Copy the code
Handler’s dispatchMessage method distributes messages externally. At this point, the entire Message distribution process is complete
/** * Handle system messages here. */ public void dispatchMessage(@NonNull Message msg) { if (msg.callback ! = null) { handleCallback(msg); } else { if (mCallback ! = null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); }}Copy the code
5. Message barriers
The Android system uses a Barrier mechanism to ensure that some high-priority messages (asynchronous messages) are executed as quickly as possible. The general process is as follows: first send a barrier message to MessageQueue. When MessageQueue traverses the barrier message, it will judge whether there is an asynchronous message in the current queue. If there is, the synchronous message will be skipped first (all the messages sent by the developer actively belong to the synchronous message), and the asynchronous message will be executed first. This mechanism allows synchronous messages not to be processed until the asynchronous message has been executed
The async parameter in the constructor of the Handler is used to control whether the Message being sent is an asynchronous Message
public class Handler { final boolean mAsynchronous; public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) { mAsynchronous = async; } private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) { msg.target = this; msg.workSourceUid = ThreadLocalWorkSource.getUid(); If (mAsynchronous) {// Set MSG. SetAsynchronous (true); } return queue.enqueueMessage(msg, uptimeMillis); }}Copy the code
If MessageQueue determines that the header message is a barrier message when retrieving the header message, it will traverse backwards to find the first asynchronous message for processing first
@UnsupportedAppUsage Message next() { 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 ! = &&msg.target == null) {// Target being null is a barrier message // a barrier. Find the next asynchronous message in The queue. // Loop through the queue to find the first asynchronous message after the barrier message to process do {prevMsg = MSG; msg = msg.next; } while (msg ! = null && ! msg.isAsynchronous()); }}}}Copy the code
Exit the Looper loop
The Looper class itself has a method restriction. Except for the main thread, all MessageQueue associated with the child thread can exit the Loop, that is, quitAllowed only the main thread can be false
public final class Looper {
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
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
MessageQueue supports two ways to exit the Loop:
- If safe is true, only messages that have not been executed are removed. Messages whose timestamp is the current time are not removed
- If safe is false, remove all messages
void quit(boolean safe) { if (! MQuitAllowed) {//MessageQueue throws new IllegalStateException("Main thread not allowed to quit."); Synchronized (this) {if (mspapers) {// Avoid calling return; } mQuitting = true; If (safe) {/ / remove all message has not yet been performed only, does not remove the timestamp is equal to the current time of message removeAllFutureMessagesLocked (); } else {// Remove all messages removeAllMessagesLocked(); } // We can assume mPtr ! = 0 because mQuitting was previously false. nativeWake(mPtr); }}Copy the code
7, IdleHandler
IdleHandler is an internal interface to MessageQueue that can be used to perform low-priority operations while the Loop thread is idle
public static interface IdleHandler {
boolean queueIdle();
}
Copy the code
When a MessageQueue retrieves a header Message, if it finds that no Message needs to be executed, it passes through mIdleHandlers and executes idleHandlers in turn
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>(); @unsupportedAPpusage Message next() {··· int pendingIdleHandlerCount = -1; // -1 only during first iteration int nextPollTimeoutMillis = 0; for (;;) {·· synchronized (this) {··· · // If the queue header mMessages are null or mMessages need to be delayed // then IdleHandler if is executed (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size(); } if (pendingIdleHandlerCount <= 0) { // No idle handlers to run. Loop and wait some more. mBlocked = true; continue; } if (mPendingIdleHandlers == null) { mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); } for (int i = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; // release the reference to the handler boolean keep = false; Keep = idler.queueidle (); // Execute IdleHandler (); // Execute IdleHandler (); } catch (Throwable t) { Log.wtf(TAG, "IdleHandler threw exception", t); } if (! keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } } }Copy the code
For example, ActivityThread adds a GcIdler to the main thread MessageQueue to try to perform GC operations when the main thread is idle
public final class ActivityThread extends ClientTransactionHandler { @UnsupportedAppUsage void scheduleGcIdler() { if (! mGcIdlerScheduled) { mGcIdlerScheduled = true; // addIdleHandler looper.myqueue ().addidlehandler (mGcIdler); } mH.removeMessages(H.GC_WHEN_IDLE); } final class GcIdler implements messagequeue. IdleHandler {@override public final Boolean queueIdle() {// Attempt GC doGcIfNeeded(); purgePendingResources(); return false; }}}Copy the code
8. Summarize
So to sum up all of this
-
Each Handler is associated with an instance of Looper, which can be passed in via the constructor when initializing the Handler, otherwise the Looper object associated with the current thread is used by default
-
Each Looper is associated with a MessageQueue instance, and each thread needs to initialize its own Looper instance by calling looper.prepare (). The looper.loop () method is called to cause the thread to loop back to MessageQueue and execute the message. By default, the Android system initializes a Looper object associated with the main thread for each application and turns on a loop to handle the main thread messages by default
-
MessageQueue stores messages in a link structure. Messages with earlier execution times (i.e., smaller timestamps) are placed at the head of the linked list. Looper loops messages out of the list and calls back to the Handler, which may involve blocking operations
-
Message, Handler, Looper, and MessageQueue constitute a producer and consumer pattern. Message is the product, MessageQueue is the transport pipe, Handler is the producer, and Looper is the consumer
-
Handler to Looper, Handler to MessageQueue, Looper to MessageQueue, and Looper to Thread are all one-to-one relationships. They cannot be changed after being associated. But Looper to Handler and MessageQueue to Handler can be one-to-many
-
Handler can be used to update the UI with an implicit prerequisite: Handler is associated with the main thread Looper. Handlers initialized in the main thread are associated with the main thread Looper by default, so their handleMessage(Message MSG) methods are called by the main thread. If the Handler initialized by the child thread also wants to perform UI updates, it needs to get the mainLooper to initialize the Handler
-
For loopers we create ourselves in child threads, we should actively exit the loop when it is no longer needed, otherwise the child thread will never be released. For the main Loop, we should not actively exit, otherwise the application will crash
-
We can add IdleHandler to MessageQueue to perform low-priority tasks while the Loop thread is idle. For example, if we have a requirement that we want to perform some UI operations after the main thread has finished drawing the interface, we can do this with IdleHandler, which will avoid slowing the user down to the first screen of the page
Handler application in the system
1, HandlerThread
HandlerThread is a class in the same package as Handler from the Android SDK. The name of the class indicates that it is a thread and uses Handler
The usage is similar to the following code. The Handler is initialized with a Looper object inside the HandlerThread, and the time-consuming task is declared in the Handler. The main thread triggers the HandlerThread to execute the time-consuming task by sending a message to the Handler
class MainActivity : AppCompatActivity() { private val handlerThread = HandlerThread("I am HandlerThread") private val handler by lazy { object : Handler(handlerThread.looper) { override fun handleMessage(msg: Message) {thread.sleep (2000) log.e ("MainActivity", "here is a child Thread that can be used to perform time-consuming tasks: " + Thread.currentThread().name) } } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) btn_test.setOnClickListener { handler.sendEmptyMessage(1) } handlerThread.start() } }Copy the code
The source code for HandlerThread is fairly simple, with just over 100 lines
HandlerThread, a subclass of Thread, is designed to execute time-consuming tasks. Its run() method automatically creates a Looper object for itself and stores it to mLooper. The HandlerThread will then loop through the Message
Public class HandlerThread extends Thread {// Thread priority; Thread ID int mTid = -1; // The current thread holds the Looper object Looper mLooper; private @Nullable Handler mHandler; public HandlerThread(String name) { super(name); mPriority = Process.THREAD_PRIORITY_DEFAULT; } public HandlerThread(String name, int priority) { super(name); mPriority = priority; } @Override public void run() { mTid = Process.myTid(); // Trigger the current thread to create Looper object looper.prepare (); Synchronized (this) {// obtain Looper object mLooper = looper.mylooper (); // Wake up all waiting threads notifyAll(); } / / sets the thread priority Process. SetThreadPriority (mPriority); onLooperPrepared(); // Start the message loop looper.loop (); mTid = -1; }}Copy the code
In addition, HandlerThread contains a getLooper() method to getLooper. When we call handlerThread.start() externally to start the thread, the getLooper() method must wait until Looper is initialized before returning, because the execution timing of its run() method is still uncertain. Otherwise, it will block due to wait(). After the run() method has initialized Looper, notifyAll() is called to wake up all threads in the wait state. So before using a HandlerThread, outsiders remember to call the start() method to start the HandlerThread
// Get the Looper object associated with HandlerThread // Since getLooper() may be executed before run() // when mLooper is null, the caller thread blocks until the Looper object is created public Looper getLooper() { if (! isAlive()) { return null; } // If the thread has been started, wait until the looper has been created. synchronized (this) { while (isAlive() && mLooper == null) { try { wait(); } catch (InterruptedException e) { } } } return mLooper; }Copy the code
HandlerThread facilitates interaction between the main thread and child threads. The main thread can declare time-consuming tasks directly through the Handler and hand them off to the child thread to execute. Handlerthreads can also be easily shared between multiple threads. The main thread and other child threads can send tasks to the HandlerThread, and the HandlerThread can ensure that the execution of multiple tasks is orderly
2, IntentService
IntentService IntentService is a subclass of Service provided by the system. It is used to execute time-consuming tasks sequentially in the background. It stops automatically after all tasks are processed, without manually calling stopSelf(). And because IntentService is one of the four components that has a higher priority and is not easily killed by the system, it is suitable for performing some high-priority asynchronous tasks
IntentService has been officially recommended by Google for developers in the past, but has been deprecated in Android 11, but that doesn’t stop us from understanding how it works
IntentService internally relies on HandlerThread. Its onCreate() method creates a HandlerThread that takes the Looper object to initialize ServiceHandler. ServiceHandler passes each Message it receives to the abstract onHandleIntent method, which subclasses implement to declare time-consuming tasks
public abstract class IntentService extends Service { private volatile Looper mServiceLooper; @UnsupportedAppUsage private volatile ServiceHandler mServiceHandler; private final class ServiceHandler extends Handler { public ServiceHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { onHandleIntent((Intent)msg.obj); stopSelf(msg.arg1); } } @Override public void onCreate() { super.onCreate(); HandlerThread thread = new HandlerThread("IntentService[" + mName + "]"); // Trigger HandlerThread to create Looper object thread.start(); MServiceLooper = thread.getLooper(); mServiceLooper = thread.getLooper(); mServiceHandler = new ServiceHandler(mServiceLooper); } @WorkerThread protected abstract void onHandleIntent(@Nullable Intent intent); }Copy the code
Each time you start IntentService, the onStart() method is called, wrapping the Intent and startId as a Message object and sending it to the mServiceHandler. Of particular interest is the startId parameter, which uniquely identifies each task request to IntentService. The value of startId is automatically incremented each time the onStart() method is called back. An IntentService should not stop an IntentService immediately after it has processed a Message, because a MessageQueue has yet to be retrieved. The stopSelf(int) method will not cause IntentService to be stopped if the stopSelf(int) method is passed a parameter that is not equal to the current startId value, thus preventing the omission of unprocessed messages
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
Copy the code
Fourthly, the application of Handler in tripartite library
1, the EventBus
EventBus is a Publish /subscribe event bus for Android and Java. This shows that EventBus is universally available in Java environments, with special platform support for Android. EventBus’s four message sending policies include threadmode. MAIN to specify message callbacks on the MAIN thread, internally via a Handler
EventBusBuilder attempts to fetch MainLooper, which, if available, can be used to initialize HandlerPoster for the main thread callback
MainThreadSupport getMainThreadSupport() {
if (mainThreadSupport != null) {
return mainThreadSupport;
} else if (AndroidLogger.isAndroidLogAvailable()) {
Object looperOrNull = getAndroidMainLooperOrNull();
return looperOrNull == null ? null :
new MainThreadSupport.AndroidHandlerMainThreadSupport((Looper) looperOrNull);
} else {
return null;
}
}
static Object getAndroidMainLooperOrNull() {
try {
return Looper.getMainLooper();
} catch (RuntimeException e) {
// Not really a functional Android (e.g. "Stub!" maven dependencies)
return null;
}
}
Copy the code
public class HandlerPoster extends Handler implements Poster {
protected HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
}
}
Copy the code
2, Retrofit
Like EventBus, Retrofit’s internal implementation does not rely on the Android platform and can be used with any Java client. Retrofit is just a special implementation of the Android platform
When building a Retrofit object, we can choose to pass a Platform object that marks the Platform on which the caller resides
public static final class Builder { private final Platform platform; Builder(Platform platform) { this.platform = platform; }}Copy the code
The Platform class has only one unique subclass, the Android class. The main logic is to override the defaultCallbackExecutor() method of the parent class and use a Handler to call back network requests on the main thread
static final class Android extends Platform { @Override public Executor defaultCallbackExecutor() { return new MainThreadExecutor(); } static final class MainThreadExecutor implements Executor { private final Handler handler = new Handler(Looper.getMainLooper()); @Override public void execute(Runnable r) { handler.post(r); }}}Copy the code
5. Interview
1.Handler
-
What is the Handler Looper Message relationship?
-
What is the data structure of Messagequeue? Why use this data structure?
-
How do I create a Handler in a child thread?
-
How does the Handler POST method work?
-
Android message mechanism principle and source code analysis
-
Android Handler message mechanism
Due to the limited space, only part of the content is displayed, all the knowledge points are arranged in my detailed content”Lot”Help yourself to a friend in need.
2. The Activity related
-
Startup mode and usage scenarios?
-
OnNewIntent () and onConfigurationChanged ()
-
OnSaveInstanceState () and onRestoreInstanceState ()
-
How exactly does an Activity start
-
Startup mode and usage scenario
-
OnSaveInstanceState and onRestoreInstanceState are used
-
OnConfigurationChanged is used and resolved
-
Parsing the Activity startup process
3.Fragment
-
Fragment life cycle versus Activity
-
How do Fragments communicate with each other
-
Fragments of startActivityForResult
-
Fragment overlap problem
-
Fragments que
-
Fragment overlap, how to communicate
-
Fragment life cycle
Due to the limited space, only part of the content is displayed, all the knowledge points are arranged in my detailed content”Lot”Help yourself to a friend in need.
4. The Service related
-
Process to keep alive
-
The running thread of the Service (life cycle methods are all in the main thread)
-
How does the Service start and stop
-
Which thread does the callback method in ServiceConnection run on?
-
StartService is different from bingService
-
Process preservation general routine
-
Everything you need to know about process survival
5.Android layout optimization
-
When to use ViewStub, include, merge?
-
How do they work?
-
ViewStub, include, merge
-
Android layout optimization of ViewStub, include, merge use and source code analysis
6. BroadcastReceiver
-
Registration mode, priority
-
Broadcast type, difference
-
The usage scenarios and principles of broadcasting
-
Android broadcast dynamic static registration
-
Common usage and process analysis
-
Broadcast source code parsing
7. AsyncTask
-
Is AsyncTask executed serially or in parallel?
-
AsyncTask changes with Android versions
-
AsyncTask fully parses
-
Serial or parallel
8.Android event distribution mechanism
-
OnTouch is different from onTouchEvent, call order
-
DispatchTouchEvent onTouchEvent, onInterceptTouchEvent method sequence and usage scenarios
-
How to resolve sliding conflicts
-
Event distribution mechanism
-
Event Distribution parsing
-
DispatchTouchEvent onTouchEvent, onInterceptTouchEvent method to parse the usage scenario
Due to the limited space, only part of the content is displayed, all the knowledge points are arranged in my detailed content”Lot”Help yourself to a friend in need.
For Android development friends should be very complete interview materials, in order to better organize each module, I refer to a lot of high-quality online blog posts and projects, and strive not to miss every knowledge point. Many friends reviewed with these contents and got offers from BATJ and other big manufacturers. This material has also helped many Android developers, and I hope it can also help you.