The Android messaging mechanism Handler is used in the development process, it can easily switch from the child thread to the main thread, most of the time we will use the Handler for UI update operations. So how does Handler communicate between threads? How do you switch threads? This article will take you through the easy implementation of a Handler and take a closer look at how it works

Before we start, let’s take a look at how this Handler works. I’m not going to tell you how to use this Handler. If you’ve done Android development, I’m sure you can use it.

Handler process

Handler sends messages using Looper and MessageQueue. Looper initialization: At application startup, ActivityThread creates a looper.prepare () and calls looper.looper () in an infinite loop waiting for any new messages. Sending a Message: A Message is sent via Handler’s Send method. The MessageQueue. Enqueue () method is called, and the Message is added to the MessageQueue Message queue The Handler Handler () method is called. Looper is running in the thread that created the Handler. HandleMessage must be creating the Handler line Process to execute.

This process is graphically represented

image.png

Through the above simple solution to the Handler process, next we will implement a simple Handler according to the above process, and then we are in the problem to see the source of the Handler.

Write a Handler by hand

  • Start by creating four classes :Handler, Message, MessageQueue, and Looper

We implement the core Looper class first: store the Looper object with a static ThreadLocal and initialize Looper with the prepare() method

Looper private static final ThreadLocal<Looper> mThreadLooper = new ThreadLocal<>(); Public MessageQueue MessageQueue; public Looper() { messageQueue = new MessageQueue(); } public static void prepare() {//ThreadLocal gets the current thread if (null! = mthreadLooper.get ()) {throw new RuntimeException(thread.currentThread () + ": looper already exists "); } mThreadLooper.set(new Looper()); }Copy the code

Then write a static method that gets Looper, myLooper(), which is called by Handler

Public static looper myLooper() {return mThreadLooper (); }Copy the code

Another core method of Looper is to implement poller to query new messages. As we know, we need to query whether there are new messages through Message queue MessageQueue. MessageQueue stores Message

public int what; public Object obj; Public Message next; // Messages sent using this handler need to be sent to this handler to handle the public Handler target; public void recyle() { obj = null; next = null; target = null; }Copy the code

MessageQueue stores messages. MessageQueue needs to implement two methods, one is to join the queue enqueue, the other is to get the queue next at the tail,Looper polling is to call the next method to get the latest Message, when there is no latest Message at next, it is blocked Notify () : I have a new message. Notice that synchronized is required for enqueue and enqueue.

The operation of entrance

** @param message */ public void enqueue(message message) {// Synchronized (this) { Message m = mMessage; if (null == m) { mMessage = message; } else {// loop to check if Message per is at the end of the list; do { per = m; m = per.next; } while (null ! = m); per.next = message; } // notify get message unblock notify(); }}Copy the code

Take team operation

** @return Message */ Message next() {synchronized (this) {Message Message; for (; ;) { message = this.mMessage; if (null ! = message) { break; } else {// block state try {wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } mMessage = mMessage.next; return message; }}Copy the code

Now that we have MessageQueue, we can implement Looper poller to listen for new messages

Public static void looper() {looper looper = looper.mylooper (); / / Public static void looper() {looper looper = looper.mylooper (); MessageQueue messageQueue = looper.messageQueue; for (; ;) Message message = messagequyue.next (); if (message == null) { break; } / / distribute to send the message handler execution message. The target. The handleMessage (message); }}Copy the code

Next, we implement Handler method, we get Looper in Handler, and through Looper get MessageQueue to send MessageQueue.

public class Handler { private Looper looper; private MessageQueue messageQueue; public Handler() { looper = Looper.myLooper(); MessageQueue = looper. MessageQueue; } public void sendMessage(Message message) { message.target = this; // Add this message to the message queue messagequeue.enqueue (message); } public void handleMessage(Message message) { } }Copy the code

Initialize Looper, then start Looper poller to listen for messages. Handler sends and receives messages

public void testHandler() { Looper.prepare(); Final Handler Handler = new Handler() {@override public void handleMessage(Message Message) { System.out.println(Thread.currentThread() + " "); }}; new Thread(new Runnable() { @Override public void run() { handler.sendMessage(new Message()); } }).start(); Looper.looper(); }Copy the code

OK, through the above handwritten implementation of a simple handler, I believe readers must understand the core idea of handler.

Handler source code analysis

The Handler source code is implemented after we thoroughly understand the Handler process

Or according to the above, handwritten Handler implementation to analyze the Android Handler source code.

Looper source code analysis

First, look at the familiar member properties

// sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static Looper sMainLooper;  // guarded by Looper.class

    final MessageQueue mQueue;
Copy the code

Consider implementing the prepare() method, which stores Looper into ThreadLocal

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

The myLooper method is also implemented to provide other classes to call 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();
   }
Copy the code

How does Android initialize Looper? The main method of ActivityThread initializes Looper and turns on the poller

public static void main(String[] args) { ....... Looper.prepareMainLooper(); . Looper.loop(); }Copy the code

The prepareMainLooper method is simple and actually calls the prepare and myLooper methods

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

The poller loop method is also simple to implement

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; . // Infinite loop to get Message for (;;) { Message msg = queue.next(); // might block if (MSG == null) {// No message indicates that the message queue is testtest.return; } final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0; final long dispatchEnd; Try {/ / if there is new information for the target is the current Handler then calling dispatchMessage processing messages MSG. Target. DispatchMessage (MSG); dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0; } finally { if (traceTag ! = 0) { Trace.traceEnd(traceTag); }}... }... }Copy the code

Now, the dispatchMessage of the Handler, it’s actually processing the message

Public void dispatchMessage(Message MSG) {// If the Message callback is actually a Runnable, run if (MSG. = null) { handleCallback(msg); } else {// If Handler sets the callback, call the callback if (mCallback! = null) { if (mCallback.handleMessage(msg)) { return; } // Call handleMessage(MSG); }}Copy the code

Handler source code analysis

The handler gets Looper and MessageQueue in its constructor during initialization.

public Handler(Callback callback, boolean async) {
        ......

        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

There are several types of Handler that send messages, but let’s just look at the simplest one

public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }
    
    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

The above code implementation is fairly simple, essentially adding a message to a message queue.

MessageQueue source

Take a look at MessageQueue’s enqueue and unqueue operations, which are actually added to the end of the Message list

boolean enqueueMessage(Message msg, 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 | | the when = = 0 | | the when < p.w hen) {/ / this section as the core code MSG. The next = p; mMessages = msg; needWake = mBlocked; } else { // 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 == next adds the latest message prev.next = MSG; If (needWake) {nativeWake(mPtr); if (needWake) {nativeWake(mPtr); if (needWake) {nativeWake(mPtr); } } return true; }Copy the code

Take a look at the next method, which is also the core method that the Looper.loop() poller polls to see if there are new messages

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; }}Copy the code

OK, that’s the core of the Handler source code. I’ll cover ThreadLocal in a separate article.