In Android development, you can’t update the UI in the child thread, as you probably know, but there are many times when you want to update the UI after accessing the network or doing other time-consuming tasks in the child thread. Therefore, Android provides a Handler mechanism. It solves this problem very well.

Before we look at Handler, let’s briefly mention a few key classes:

  • Handler: Sends and processes messages.
  • Message: Message carrier, used to store the ARG and content of a Message.
  • MessageQueue: Message queue, used to store Message carrier Message.
  • Looper: Think of it as a Message poller that continually pulls messages out of the MessageQueue

1. Use of Handler

private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            
            if(MSG. What == 1){// Receive message and process}}}; new Thread(newRunnable() {
            @Override
            public void run() {
                Message message = Message.obtain();
                message.what = 1;
                message.obj = "News";
                handler.sendMessage(message);
            }
        }).start();
Copy the code

From the code, Handler is simple to use. Create a Message object in a child thread, send it through handler.sendMessage(), and process it inside handleMessage(). However, when creating handlers in real development, it is recommended that you use static inner classes to create handlers to reduce memory leaks. Let’s look at the constructor of the Handler.

 public Handler() {
        this(null, false);
    }

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

In the second constructor comment 1, you can see that a RuntimeException is thrown if mLooper is null in the current thread, which is why looper.prepare () is required to create a Handler in a child thread. The main thread doesn’t need to be manually created because the Looper object is already created for the main thread when the program is started. Look at the main method in the ActivityThread class below:

Public static void main (String [] args) {/ / omitted code... // Create a Looper object looper.prepareMainLooper () for the main thread; 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); // Start message polling looper.loop (); throw new RuntimeException("Main thread loop unexpectedly exited");
    }
Copy the code

When the program starts, not only is a Looper object created for the main thread, but the message poller looper.loop () is also started. Next, let’s analyze sendMessage() to see how messages are added to MessageQueue.

2. SendMessage () analysis

public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }

public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

 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

After sendMessage() sends a message, MessageQueue enqueueMessage() is finally called, and uptimeMillis is the time to send the message.

EnqueueMessage () Boolean enqueueMessage(Message MSG, long when) {//1if (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)#quit() or Looper#quitSafely(), which ends the polling and frees the resource
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false; } // Add the message 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

MSG. Target = null; MSG. Target = null; MSG. It then determines whether the message polling is over, and joins the message queue if it is not. At this point, the addition of the message is complete.

3. Looper analysis

First, the creation of Looper

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


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");
        }
        //1
        sThreadLocal.set(new Looper(quitAllowed));
    }
Copy the code

PrepareMainLooper () is called when the main thread creates Looper, and prepare() is finally called. In comment 1 of the code above, sThreadLocal is an instance object of ThreadLocal. SThreadLocal internally uses the current thread as the key and the created Looper instance as the value. It is implemented primarily in ThreadLocalMap, an internal class of ThreadLocal. This allows each thread to have a separate copy of the Looper instance.

Looper#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 (;;) {
            //1
            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 { //2 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(); }}Copy the code

In comment 1, you can see that looper uses for(;). It goes into an infinite loop. Calling the next() method in MessageQueue fetches a message from MessageQueue. If no message exists, next() blocks (implemented by the bottom layer, calling the local method nativePollOnce()). When a message returns, it goes to comment 2. Call Handler’s dispatchMessage() to distribute the message.

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

HandleCallback () is called when Handler#post(Runnable run) is called or msg.callback is set. Finally, handleMessage() is called back.

The article ~