Hanlder Source Code Analysis

The Role of Handler

Android message mechanism

We all know that the UI of Android applications is drawn in the main thread (UI thread). If the main thread does some time-consuming operations, the main thread will be blocked. Time-consuming operations mainly include network access, large amount of computation, file reading and writing, etc., which will cause UI jamming, and serious situation will cause application ANR. So these time-consuming operations mentioned above cannot be carried out in the main thread. When we deal with these potentially time-consuming operations, we open a new thread to deal with them.

When the non-main thread has finished the operation, we may have to show the result of the operation to the interface. The result is in the non-main thread, but the thread that draws the interface is in the main thread. How can the main thread get the result of the non-main thread?

At this point, the role of Handler is reflected. We use Handler to take out the results of non-main threads and put them into main threads. It is OK for the main thread to update the UI interface after it gets the result through Handler. How to put the main thread through Handler and how the main thread gets the results through Handler to execute are analyzed below.

Handler Principle

Related Java classes

ActivityThread

As we all know, the main function of a program in Java is called the main function, which is the only entrance to the execution of a Java program. Android application layer code is written in Java, so when Android executes Java code, it first executes the main function. So what kind of main function does Android execute?

The main function that Android executes is in ActivityThread:

 public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
        SamplingProfilerIntegration.start();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        Process.setArgV0("<pre-initialized>");

        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");
    }

As you can see from the above code, the main function has a small amount of code. The key codes are as follows:

Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
    sMainThreadHandler = thread.getHandler();
}
Looper.loop();

Looper

This class is called Message Queue. Its main function is to create a Message Queue, bind its own objects to the current Thread, poll the message queue continuously, and send the message out of the message queue to the Handler corresponding to the message.

MessageQueue

This class is called Message queue. The data structure is queue. It maintains the status of Message queue and performs the operation of entering or leaving Message according to the time of entering queue. Another function is to add or delete SyncBarrier.

Message

This class, called message, is the carrier of a specific message. It implements the Parcelable interface, so it can be stored locally. Message encapsulates the time when a message is triggered, the type of message, the specific object (obj, data) carried in the message, the Handler of the message, and so on.

Handler

This class is called message handler, through which messages can be sent, removed, and processed.

Handler Operating Process

Source code analysis

Start with the main function of ActivityThread.

//Create Looper, MessageQueue, and bind the current thread to the Looper object
Looper.prepareMainLooper();
//Create ActivityThread objects
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
    //Get the main thread's Handler, which is used to process the message
    sMainThreadHandler = thread.getHandler();
}
//Start polling for information
Looper.loop();

The important code in the Looper.prepareMainLooper method is to execute the preparer method. Its function is to create Looper and MessageQueue in Looper, and to bind the current thread to Looper through ThreadLocal, so that a thread can only correspond to one Looper object. This Looper is the Looper of the main thread, so any message sent by the Hadler created in the main thread will enter the message queue of this Looper.

public static void prepareMainLooper() {
        //Create Looper and bind to the current thread
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            //Get the newly created main thread Looper
            sMainLooper = myLooper();
        }
    }
 private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        //Binding Looper to the current thread through ThreadLocal
        sThreadLocal.set(new Looper(quitAllowed));
    }

Looper.loop() ;

 /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
        //Get the Loper of the current thread
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        //Get the message queue for Looper in the current 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();
        //Dead-loop, polling for messages
        for (;;) {
            //Message Canceling Message Queue
            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 traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            try {
                //After the message is taken out, it is distributed for message processing.
                msg.target.dispatchMessage(msg);
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }

            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);
            }
            //Recycle the message object after it is used so that it can be used next time
            msg.recycleUnchecked();
        }
    }

The above code Message msg = queue.next(); fetching a message from the message queue, or possibly the message queue spatiotemporal, can only block until the next message arrives.

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
        //Record the time interval for the next cancellation
        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;
                //Processing Sync Barrier, skipping synchronous messages, only handling asynchronous messages
                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());
                }
                //Processing synchronous or asynchronous messages
                if (msg != null) {
                    //No processing time to message
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        //Calculate the world interval for the next interest cancellation
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        //Update message queue header pointer
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        //Remove information from the list
                        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;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (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);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }

If a message is retrieved from the message queue, the next step is to distribute and process the message msg.target.dispatchMessage(msg); the msg.target is the Handler that sends the message.

 /**
     * 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);
        }
    }

From the above code, when the callback is set in the Message, the callback method in the Message is executed. Otherwise, when the mCallback!=null in the Handler executes the mCallback!=null, when the mCallback==null, the handleMessage method of the Handler itself is called to process the message.

Above is the process of message processing, and below is the analysis of sending messages through Handler.
We usually use Handler to send messages in the following ways:

public final boolean sendMessage(Message msg)
public final boolean sendEmptyMessage(int what)
public final boolean sendEmptyMessageDelayed(int what, long delayMillis)
public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) 
public final boolean sendMessageDelayed(Message msg, long delayMillis)
public boolean sendMessageAtTime(Message msg, long uptimeMillis) 
//Here is the post method
public final boolean post(Runnable r)
public final boolean postAtTime(Runnable r, long uptimeMillis)
public final boolean postDelayed(Runnable r, long delayMillis)

All of these methods end up calling the sendMessageAtTime 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;
        }
        //Entry processing of messages
        return enqueueMessage(queue, msg, uptimeMillis);
    }

enqueueMessage Method of Handler

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        //Set the target of Message to the current Handler object
        msg.target = this;
        //Determine whether the message is asynchronous?
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        //Join the message queue according to the time of the message
        return queue.enqueueMessage(msg, uptimeMillis);
    }

enqueueMessage Method of MessageQueue

boolean enqueueMessage(Message msg, long when) {
        //The message msg.target sent by a non-system cannot be empty, otherwise an exception will be thrown
        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) {
            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;
            }

            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;
    }

Flow chart

Handler Related Extensions

HandlerThread

IntentService

summary

The framework of Handler message mechanism is very well designed. In the process of using it, we only need to customize a handler, use it to send and process messages, and the rest need not be bothered by us. When we understand the principle, we are more comfortable with using Handler later.

Many details of the Handler message mechanism are not covered here, and will be added later when you have a deeper understanding of Handler.

Reflection

  • How to use Handler in non-main threads
  • Where Handler is used for View UI updates
  • Custom Handler

Reference resources

Keywords: Android Java network REST

Added by Deemo on Mon, 10 Jun 2019 02:21:08 +0300