preface

Those of you who have done Android development know that you can’t modify UI controls in a non-main thread because Android says that you can only access the UI in the main thread. If you access the UI in a child thread, the application will throw an exception

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy .Copy the code

Also, Android does not recommend doing time-consuming operations in the UI thread (the main thread), which could lead to ANR. If we need to do something time-consuming and then modify the UI, we need to use the Android Handler to switch to the main thread to access the UI. Therefore, the main reason the system provides a Handler is to solve the problem of not being able to access the UI in child threads.

An overview of the

There are several other concepts you need to understand to understand the Handler messaging mechanism:

  1. The UI thread

    The main thread ActivityThread

  2. Message

    Handler Sends and processes messages managed by MessageQueue.

  3. MessageQueue

    A message queue is used to store messages sent by a Handler and is executed on a first-in, first-out basis.

  4. Handler

    Responsible for sending and processing messages.

  5. Looper

    Responsible for the Message loop, loop out the Message in the MessageQueue and hand it to the corresponding Handler for processing.

When the application starts, a UI thread is opened and a message loop is started. The application continuously retrieves and processes messages from the message list to run the application. Looper’s job is to create a MessageQueue and then go into an infinite loop to read messages from that MessageQueue, and the creator of the message is one or more handlers. The flow chart is as follows:

The following combined with the source code to specific analysis

Looper

The two most important Looper methods are prepare() and loop()

Let’s look at the constructor

final MessageQueue mQueue;

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
}Copy the code

Looper creates a MessageQueue when it is created

Create a Lopper for the Handler using the prepare method.

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); private static Looper sMainLooper; // guarded by Looper.class 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)); } 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

As you can see, the Looper object created here is saved using ThreadLocal. Here’s a quick look at ThreadLocal.

ThreadLocal is an internal data store class that can store data in a specified thread. Once the data is stored, only the specified thread can fetch the data, and no other thread can fetch the data. This ensures that each thread has a Looper. It can also be seen from the source code that a thread can only have one Looper, otherwise an exception will be thrown.

The prepareMainLooper() method is called by the system in ActivityThread.

ActivityThread.java

public static void main(String[] args) { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain"); SamplingProfilerIntegration.start(); / /... Omit the code looper.prepareMainLooper (); ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } if (false) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); } // End of event ActivityThreadMain. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); }Copy the code

As you can see, Looper is automatically created to handle messages, so we do not need to manually call looper.prepare () when we use Handler in the main thread. This Handler is bound to the main thread’s Looper by default. Handler can update the UI in its handleMessage if the Handler’s bound Looper is the main thread’s Looper, otherwise updating the UI will raise an exception. We may use handlers in multiple places during development, so we can conclude that a Looper can bind to multiple handlers. How does Looper tell which Handler to process a Message? 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."); } final MessageQueue queue = me.mQueue; // Make sure the identity of this thread is that of the local process, // and keep track of what that identity token actually is. Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } // This must be in a local variable, in case a UI event sets the logger final Printer logging = me.mLogging; if (logging ! = null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs; final long traceTag = me.mTraceTag; if (traceTag ! = 0 && Trace.isTagEnabled(traceTag)) { Trace.traceBegin(traceTag, msg.target.getTraceName(msg)); } final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis(); final long end; try { msg.target.dispatchMessage(msg); end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis(); } finally { if (traceTag ! = 0) { Trace.traceEnd(traceTag); } } if (slowDispatchThresholdMs > 0) { final long time = end - start; if (time > slowDispatchThresholdMs) { Slog.w(TAG, "Dispatch took " + time + "ms on " + Thread.currentThread().getName() + ", h=" + msg.target + " cb=" + msg.callback + " msg=" + msg.what); } } if (logging ! = null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } // Make sure that during the course of dispatching the // identity of the thread wasn't corrupted. final long newIdent  = Binder.clearCallingIdentity(); if (ident ! = newIdent) { Log.wtf(TAG, "Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x" + Long.toHexString(newIdent) + " while dispatching to " + msg.target.getClass().getName() + " " + msg.callback + " what=" + msg.what); } msg.recycleUnchecked(); // Retrieve message}}Copy the code

More code, let’s pick up the focus. Lines 2 to 6 get the current Looper if not throw an exception, So if we use Handler in a child thread we must manually call looper.prepare () and looper.loop () the system provides sample code in the code

class LooperThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); mHandler = new Handler() { public void handleMessage(Message msg) { // process incoming messages here } }; Looper.loop(); }}Copy the code

After obtaining the MessageQueue, the message cycle is started and messages are continuously obtained from the MessageQueue. If there is no message, the system blocks and waits for the message. Have you call MSG. Target. DispatchMessage (MSG) process the message.

MSG. Target is the Handler to which Message belongs, which will be explained later in Handler

Looper doesn’t need to worry about which Handler handles the Message. Instead, Looper starts the Message loop to receive the Message and process it. Call msG.recycleunchecked () to recycle the message after it is processed.

So once the message loop is started, can it be stopped? The answer is yes, Looper provides quit() and quitSafely() to quit.

  public void quit() {
     mQueue.quit(false);
  }

  public void quitSafely() {
     mQueue.quit(true);
  }Copy the code

You can see that what is actually called is the exit method in MessageQueue, which is described in MessageQueue. Calling quit() directly quitSafely exits the Looper, whereas quitSafely() simply sets an exit flag and processes all the existing messages in the message queue before exiting safely. Sending messages through Handler will fail after Loooper exits. If Looper is manually created in a child thread, exit Looper after processing to terminate the message loop.

Here is the end of Looper source analysis, we summarize the work of Looper: 1. It is created to bind to threads, ensuring that each thread has only one Looper instance, and that each Looper instance has only one MessageQueue 2. Once created, loop() is called to start the Message loop, which continuously fetches messages from MessageQueue and passes them to the Handler to which the Message belongs, namely the msg.target property. 3. After the message is processed, call msg.recycleUnchecked to recycle the message

The Message and MessageQueue

Message is the Message passed in thread communication, and it has several key points

  1. Use what to distinguish messages
  2. Use arg1, arg2, obj, data to pass data
  3. The target parameter determines the Handler to which Message is associated, as you’ll see later in the Handler source code.

MessageQueue

MessageQueue is responsible for managing message queues, maintained through a singly linked list data structure. There are three main methods in the source code: 1. The enqueueMessage method inserts a piece of data into the message list; 2. The next method fetches a message from the message queue and removes it from the message queue; 3

Next method

Message next() { // Return here if the message loop has already quit and been disposed. // This can happen if the application tries to restart a looper after quit // which is not supported. final long ptr = mPtr; if (ptr == 0) { return null; } int pendingIdleHandlerCount = -1; // -1 only during first iteration int nextPollTimeoutMillis = 0; 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 ! = 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(); return msg; } } else { // No more messages. nextPollTimeoutMillis = -1; } // Process the quit message now that all pending messages have been handled. if (mQuitting) { dispose(); return null; } / /... Omit code}}Copy the code

You can see that the next method is an infinite loop, and if there are no messages in the message queue, the next method will always block here. When a new message arrives, the next method picks it up and returns it to Looper for processing and removes it from the message list.

The quit way

void quit(boolean safe) { if (! mQuitAllowed) { throw new IllegalStateException("Main thread not allowed to quit."); } synchronized (this) { if (mQuitting) { return; } mQuitting = true; if (safe) { removeAllFutureMessagesLocked(); } else {removeAllMessagesLocked(); // We can assume mPtr! = 0 because mQuitting was previously false. nativeWake(mPtr); } } private void removeAllMessagesLocked() { Message p = mMessages; while (p ! = null) { Message n = p.next; p.recycleUnchecked(); p = n; } mMessages = null; } private void removeAllFutureMessagesLocked() { final long now = SystemClock.uptimeMillis(); Message p = mMessages; if (p ! = null) { if (p.when > now) { removeAllMessagesLocked(); } else {// Message n is not processed; for (;;) { n = p.next; if (n == null) { return; } if (n.when > now) { break; } p = n; } p.next = null; do { p = n; n = p.next; p.recycleUnchecked(); } while (n ! = null); }}}Copy the code

As you can see from the above code, when safe is true, only messages that have not yet been triggered are removed, messages that are being processed are not processed, and when safe is false, all messages are removed.

Handler

Handler is our most used class, primarily for sending and processing messages.

Let’s start with the constructor

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 (); mLooper = looper.mylooper (); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } // associate MessageQueue MessageQueue mQueue = mLooper. mCallback = callback; mAsynchronous = async; } public Handler(Looper looper, Callback callback, boolean async) { mLooper = looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async; }Copy the code

The constructor takes three arguments: 1. If looper is not passed, looper.mylooper () is called to get the looper instance of the current thread. Passing values is used, usually when Handler is used in child threads. 2. Callback when the callback Handler processes the message. 3. A Barrier is added to the Looper when the View is drawing and laying out, so that synchronous messages in subsequent message queues are not executed, so as not to affect UI drawing, but only asynchronous messages can be executed. This is what async is all about. When async is true, messages can continue to be executed without being delayed.

As you can see from the source code, since the UI thread automatically creates the Looper instance when it starts, we generally do not need to pass the Looper object when we use handlers in the UI thread. In child threads, looper. prepare and looper. loop methods must be manually called and passed to the Handler, otherwise they cannot be used. After receiving the Looper object, the Handler retrieves the MessageQueue MessageQueue from the Looper and associates it with the MessageQueue.

Then we’ll see how the Handler sends the message.

Handler sends messages in many ways, but actually ends up calling the enqueueMessage method

Focus on the enqueueMessage 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

You can see that when you send the Message, you set target = this to Message, which is the current Handler object, and you call MessageQueue’s enqueueMessage method, which puts the Message in the Message queue, Looper then took care of it.

Children’s shoes should remember before talking about stars, said to the stars to open after the Message loop, will continue to pull the Message from the MessageQueue, and call the MSG. Target. DispatchMessage (MSG) to process the Message.

Now, let’s see how the Handler receives the message the dispatchMessage method, okay

public interface Callback { public boolean handleMessage(Message msg); } /** * Subclasses must implement this to receive messages. */ public void handleMessage(Message msg) { } /** * Handle system messages here. */ public void dispatchMessage(Message msg) { if (msg.callback ! = null) { handleCallback(msg); } else { if (mCallback ! = null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); }}Copy the code

You can see that the Handler receives the message and just calls an empty method called handleMessage and if that looks familiar, look at the Handler code that we’ve written many times

private Handler mHandler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { case value: break; default: break; }}};Copy the code

That’s right, this is what we override when we create our own Handler, which processes the message and then processes it according to the msG.what identifier.

conclusion

When the application starts up, it starts the UI thread, which is the main thread, ActivityThread, Looper is started by calling Looper.prepareMainLooper() and looper.loop () in the Main method of ActivityThread. Stars when they start to create a MessageQueue instance, and only one instance, and then get messages from the MessageQueue continuously, without the block waiting for the news, there are calls to MSG. Target. DispatchMessage (MSG) to process the message. We need to create Handler instances before using Handler. When Handler is created, it will get the Looper instance associated with the current thread and the MessageQueue MessageQueue in Looper. The Message is then automatically targeted to Handler itself when it is sent, and the Message is placed in a MessageQueue to be processed by Looper. The Handler processes the message in the handleMessage method, which is overridden when created. If you want to use Handler in a child thread, you need to create a new Looper instance and pass it to Handler.

Look at the flow chart again

The last

Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler I write blog soon, write bad place, hope children’s shoes are haihan.