An overview,
In Android application development, we often use a child thread to perform some operation, and then send the data to the main thread through the Handler, telling the main thread to do the corresponding operation. For this, Android provides a set of asynchronous message processing mechanisms called Handlers.
1.1. Key members
Asynchronous Message processing using Handler is mainly composed of Message, Handler, MessageQueue and Looper.
So let’s take a quick look at some of the main members of the Handler.
Second, the stars
2.1. What is Looper
Looper is a mechanism provided by Android for asynchronous message communication between threads. Using Looper mechanism can facilitate us to achieve mutual communication between threads in multi-threaded programming. Looper usually runs in a loop queue of messages, threads by default do not provide a loop to manage the message queue. If you want to manage the message queue, call looper.prepare () in the thread to initialize the message loop and call looper.loop () to keep the message loop running until the loop is stopped. So Looper is mainly to complete MessageQueue and Handler interaction function.
2.2. Spy on Lopper
First of all, what do you think the Android app entry is? You would definitely say the onCreate function in your Application. The ActivityThread class is the initial class of the Android APP process, and its main function is the entry of the APP process. The ActivityThread class source point here
- ActivityThread class about the program entry main function part source:
Public static void main(String[] args) {// omit... PrepareMainLooper (); // Create Looper and MessageQueue objects to handle the main thread's message looper.prepareMainLooper (); // Create an ActivityThread object and bind it to AMS (ActivityManagerService) ActivityThread Thread = new ActivityThread(); Attach (false); // Create a new thread. Looper.loop(); // Message loop run throw new RuntimeException("Main thread loop unexpectedly exited"); }Copy the code
- Simple analysis:
- Looper.preparemainlooper () is called, ActivityThread is created, and looper.loop () is called to start the loop.
- Handler is bound to the main thread by default because the code above does the Looper binding for the main thread for us.
- Looper in the Main thread cannot be called to exit in the program; if it does, an exception is thrown, throw new RuntimeException(“Main Thread loop unexpectedly exited”).
2.2.1 looper.prepare () is created
// Initialize the current thread as the application's main loop. Prepare (false) indicates that it cannot exit. public static void prepareMainLooper() { prepare(false); synchronized (Looper.class) { if (sMainLooper ! = null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); }} // The Looper object is stored in ThreadLocal. A ThreadLocal is a data store class within a thread, with each thread having its own independently accessed copy of variables. static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); 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"); } // Create the Lopper object sthreadLocal. set(new Looper(quitAllowed)); } private Looper(Boolean quitAllowed) {// Create a MessageQueue message stack and obtain and bind the current thread mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }Copy the code
- Create Looper by reading the source code:
- The Android application initializes a default Looper, which is set to false to prevent message queues from exiting, as described above in ActivityThread source looper.prepareMainLooper ().
- Only one Looper can be created per thread. Each call to prepare() looks for a Lopper instance of the current thread in ThreadLocal, or creates a RuntimeException.
- The Looper object is stored in a ThreadLocal, an internal data store class.
- Looper creates an internal MessageQueue MessageQueue, saves a reference to this MessageQueue, and binds the current thread. This MessageQueue belongs to the thread on which Looper resides.
2.2.2 looper.loop ()
In the ActivityThread class, we see from the source that Looper calls the loop() function after prepareMainLooper(). Let’s take a look at the SDK source code through this question:
public static void loop() { ...... for (;;) {MSG = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; }... Try {/ / dispatch message, by dealing with MSG. The Handler target. DispatchMessage (MSG); if (observer ! = null) { observer.messageDispatched(token, msg); } dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0; } catch (Exception exception) { ...... msg.recycleUnchecked(); } } public static @Nullable Looper myLooper() { return sThreadLocal.get(); }Copy the code
We see the use of a for(;;) in looper.loop (). In an infinite loop, the Message is fetched from the Message queue via messagequeue.next (). Next () keeps fetching the next message, MGS is null and the message queue exits and the loop stops. If there is no message and no exit, the message queue remains blocked. When the next () returns a message, which call MSG. Target. DispatchMessage (MSG) to dispatch. Msg. target is a Handler that calls the Handler’s dispatchMessage() method for data distribution and the handleMessage(MSG) method at the end of dispatchMessage. This way we can normally process messages sent to the main thread.
2.3 Lopper summary
- Looper is a circulator (understood as a conveyor belt, constantly polling) that runs a message loop for a thread, constantly checking for new messages in the message queue, and only one Lopper per thread.
- Looper.prepare() creates a Looper for the current thread, stores the created Lopper instance in ThreadLocal, and maintains a MessageQueue within it.
- Looper.loop() starts polling to get the Message in MessageQueue, and dispatchMessage is used to distribute the Message.
- Looper.quit() looper.quitsafely ()- Quit Looper, the self-created Looper is recommended to quit when not in use
Three, the Handler
3.1. What is Handler
A Handler is used to send and receive messages (similar to a Courier who sends and receives messages).
Create a Handler
Handler Handler = new Handler(); Public Handler(Looper Looper, Callback Callback, Boolean async) {mLooper = Looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async; } public Handler(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 that has not called Looper.prepare()"); } // Return the message queue of the Looper object mQueue = mLooper. // mCallback = callback; mAsynchronous = async; }Copy the code
- Create Handler; create Handler;
- Handler initialization requires four parameters: Looper, Callback, async, and MessageQueue.
- If the current thread does not have a Looper instance, the Handler will not be able to initialize the instance.
3.3. Send messages through Handler
Handler. Sendxxx () API
//hand.sendMessage public final boolean sendMessage(Message msg) { return sendMessageDelayed(msg, 0); } / /... // Look down the source code and see that all functions that send messages end up calling a function in MessageQueue: 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
Here’s an example of how to send messages in child threads:
At this point, the Handler sends the Message, carrying what, obj, arg1, arg2 and other data, calls the enqueueMessage function, and packages the Message into the MessageQueue queue. At this point, Looper transports the data to the UI thread, and Hanler retrieves the data and performs the corresponding operations.
3.4. Handler Plans tasks
Handler.postxxx() :
post(Runnable); PostAtTime (Runnable, long); // Submit the scheduled task to execute postDelayed(Runnable, long) at a future time point; // The scheduled task is executed after the Nms executes itCopy the code
Let’s look at how the SDK is implemented:
Post public final Boolean Post (@nonnull Runnable r) {// How to call sendMessageDelayed() in the same way as handler.postxxx ()? return sendMessageDelayed(getPostMessage(r), 0); } / /... Private static Message getPostMessage(Runnable r) {Message m = message.obtain (); m.callback = r; return m; }Copy the code
Find that handler.postxx () assigns Runnable as a parameter to message.callback, returning the Message object, EnqueueMessage () is then called in the same way as handler.sendxxx () to place messages in the Message queue.
Here’s an example:
3.5. Extension of Handler mechanism
RunOnUiThread (Runnable), view.post(Runnable), view.post(Runnable)
Take a look at their implementation:
//runOnUiThread(Runnable) public final void runOnUiThread(Runnable action) { if (Thread.currentThread() ! = mUiThread) { mHandler.post(action); } else { action.run(); } } //view.post(Runnable) public boolean post(Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo ! = null) { return attachInfo.mHandler.post(action); } getRunQueue().post(action); return true; }Copy the code
Isn’t Handler responsible for sending and receiving messages at this point? Don’t worry, let’s move on:
3.6. Receiving messages through the Handler
Front we said to the stars, the stars in the loop () function, there is a for loop, the loop body in one line of code: MSG. Target. DispatchMessage (MSG); This is where the Handler receives the data callback (five stars). Here is the body of the function called in the loop:
/* Subclasses must implement this to receive messages. */ public void handleMessage(Message MSG) {} public void dispatchMessage(Message msg) { // 1. If MSG. Callback is not empty, it means post (Runnable r) was used to send the message. = null) { handleCallback(msg); } else { if (mCallback ! = null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); }} private static void handleCallback(Message) {// Message callback = Runnable object // Run () message.callback.run(); }Copy the code
The code tells us that when receiving data, subclasses must override handleMessage to call data back and forth to the UI thread:
3.7 Handler Summary
- Handler must be created from a Looper. You cannot create a Handler without a Looper.
- Handler uses post/ Send functions to package Message data into a MessageQueue, simply adding messages to a Message queue.
- The Looper loop picks up a message via queue.next() and dispatches it through the Handler’s dispatchMessage().
Fourth, the MessageQueue
4.1 What is MessageQueue
A MessageQueue is simply a MessageQueue. It is responsible for enqueueMessage of messages sent by Handler and retrieving messages to be executed in sequence. Message queues are single-linked list implementations.
Create a MessageQueue
Looper() private Looper(Boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); } // Indicates whether the message queue can be closed. The message queue of the main thread cannot be closed. MessageQueue(boolean quitAllowed) { mQuitAllowed = quitAllowed; mPtr = nativeInit(); }Copy the code
4.3、MessageQueue之next()
Handler usage and parsing
Message next() {final long PTR = mPtr; If (PTR == 0) {return null; } int pendingIdleHandlerCount = -1; Int nextPollTimeoutMillis = 0; int nextPollTimeoutMillis = 0; for (;;) { if (nextPollTimeoutMillis ! = 0) { Binder.flushPendingCommands(); } // Native layer blocks CPU. If blocked, wake up the event queue nativePollOnce(PTR, nextPollTimeoutMillis); synchronized (this) { final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; // If the current message is asynchronous, it is assigned to prevMsg and filtered out until the non-asynchronous message if (MSG! = null && msg.target == null) { do { prevMsg = msg; msg = msg.next; } while (msg ! = null && ! msg.isAsynchronous()); } // if (MSG! NextPollTimeoutMillis = (int) math.min (msg.when) {// Set the timeout duration for the next poll. Integer.MAX_VALUE); } else { mBlocked = false; // Specify a non-blocking task if (prevMsg! = null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; / / set the message using state, namely flags | = FLAG_IN_USE MSG. MarkInUse (); return msg; NextPollTimeoutMillis = -1; nextPollTimeoutMillis = -1; nextPollTimeoutMillis = -1; }... //IdleHandler is a callback interface for finding when a thread is blocked. i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; // Remove handler references Boolean keep = false; //queueIdle returns true to be processed by idle processors, false to be removed try {keep = idler.queueidle (); } catch (Throwable t) {log. WTF (TAG, "IdleHandler threw Exception ", t); } if (! keep) { synchronized (this) { mIdleHandlers.remove(idler); PendingIdleHandlerCount = 0; pendingIdleHandlerCount = 0; nextPollTimeoutMillis = 0; }}Copy the code
Next (); async Message (MSG); async Message (MSG); async Message (MSG);
4.4, MessageQueue enqueueMessage()
Handler usage and parsing
boolean enqueueMessage(Message msg, Long when) {// Every ordinary Message must have a target-handler 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) {// synchronized (this) {// Synchronized (this); return false; } msg.markinuse (); msg.when = when; Message p = mMessages; boolean needWake; / / p to null the triggering time of the representative MessageQueue no news or MSG is the first in the queue if (p = = null | | the when = = 0 | | the when < p.w hen) {MSG. Next = p; mMessages = msg; needWake = mBlocked; } else {// Insert messages into MessageQueue in chronological order. 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; prev.next = msg; } if (needWake) { nativeWake(mPtr); } } return true; }Copy the code
Message firing times in queues are sequential. When a Message is added to a Message queue, it is iterated from the queue header until it finds the right place to insert the Message in order to keep all messages in chronological order. (Internally iterates the Message in the queue, finds when larger than the current Message, and inserts the Message before the Message. If not found, Message is inserted to the end of the queue. If the current queue is empty, next goes to sleep and MessageQueue wakes up the Next method if needed.
4.5 MessageQueue and others
MessageQueue store/take out data in addition to the queue, and remove the message data removeMessages (), removeCallbacksAndMessages () and withdraw from the message queue the quit (), etc…
B: Message
When we send data through Handler, we wrap the data into a Message. What are the characteristics of this Message?
5.1. Message Simple Properties
Public int what; Public int arg1; Public int arg2; Public Object obj; Public long when;Copy the code
When manipulating Message, we typically assign data to the object above Message. In real development, however, it is not possible to create our Message objects very often. Looking at the source code, Message provides a Message pool:
// Return a new Message instance from the global pool. In many cases, we can avoid assigning new objects. public static Message obtain() { synchronized (sPoolSync) { if (sPool ! = null) {// If there is a Message in the pool, fetch a Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; // clear in-use flag sPoolSize--; return m; Return new Message(); return new Message(); }Copy the code
Summary of Handler series
6.1. Handler Operation diagram
As the above operation flow chart: To implement asynchronous Message processing using handlers, we first need to create a Handler object in the main thread and rewrite the handleMessage() method, then create a Message object whenever UI operations are needed in the child thread and send the Message through the Handlerr. The message is then added to the MessageQueue queue for processing, while Looper keeps trying to retrieve the message from the MessageQueue. It is then sent back to handler’s handleMessage() method via handler.dispatchMessage(MSG). Since Halldler is created in the main thread, the code in the handleMessage() method is also run in the main thread, enabling the child thread to implement UI thread operations through the Handler mechanism.
6.2 Summary of Handler series
- Message: a Message passed between threads, used for data interaction between threads. The what field in Message is used to distinguish multiple messages, arg1 and arg2 are used to pass ints, and obj can pass fields of any type.
- MessageQueue (first in, first out), used to store messages sent by the Handler. Each thread has only one MessageQueue.
- Handler, used to send and process messages. SendMessage () is used to send messages and handleMessage() is used to handle messages and perform corresponding UI operations.
- Looper, as the manager of the MessageQueue, passes the message to handleMessage() when a message is found in a MessageQueue. Again, there is only one Looper per thread.
By then, we have analyzed the Handler related series “Message, Handler, MessageQueue, Looper”. My level is limited, what is wrong with the place welcome big bosses criticism and correction!