Android Handle Principle of Looper Handler and Message

  • 2021-12-13 17:21:13
  • OfStack

Introduction

The previous content introduced Handler and explained how to use handler, but we don't know how to implement it. This paper analyzes how to realize it from the perspective of source code.

First of all, we need to know the relationship between Handler, Looper, Message and Queue

-Handler encapsulates the sending of messages and is also responsible for receiving and canceling messages. Internally associated with Looper.
-The load of Looper message encapsulation, which contains MessageQueue inside, is responsible for taking out the message from MessageQueue and then handing it over to Handler for processing
-MessageQueue is a message queue, which is responsible for storing messages. When messages come, they will be stored. Looper will read messages from MessageQueue cyclically.

Source code analysis

When we look at the new1 Handler object, look at what's going on in its 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();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

We can see from the source that he will call the Looper. myLooper method to get an Looper object, and then get the MessageQueue object from the Looper object.

Looper myLooper()

Follow up and see what the Looper. myLooper () method does. This is a static method, which can be called directly by class name and method name.


public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

In this method, there is only one line of code, which gets one Looper object from sThreadLocal, and sThreadLocal is one ThreadLocal object, which can store variables in one thread. The bottom layer is ThreadLocalMap. Since it is Map type, we must first have set1 Looper objects, and then we can get get1 Looper objects from sThreadLocal objects.

ActivityThread main()

Speaking of which, we have to introduce a new class ActivityThread, ActivityThread class is the initial class of Android APP process, and its main function is the entrance of this APP process. Let's see what this main function does.


public static final void main(String[] args) {
        ------
        Looper.prepareMainLooper();
        if (sMainThreadHandler == null) {
            sMainThreadHandler = new Handler();
        }

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        Looper.loop();
        -----
}

The Looper. prepareMainLooper () method is called in Line 2, and the Looper. loop () method is called in Line 13.

Looper prepareMainLooper()

Continue with the Looper. prepareMainLooper () method, where line 1 calls the internal prepare method. prepareMainLooper is a bit like the getInstance method in singleton mode, except that getInstance will return an object at that time, while prepareMainLooper will create a new Looper object and store it in sThreadLocal.


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

Looper prepare()

Continue to follow the prepare method, look at line 5, create a new Looper object, and call the sThreadLocal. set method to save the Looper object. From this I think you are smart enough to understand why calling the Looper. myLooper () method on an new Handler object can fetch an Looper object from an sThreadLocal object.


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

Looper construction method

At the beginning of the article, we mentioned that Looper contains MessageQueue. In fact, when new Looper object, new has an MessageQueue object.


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

Looper loop()

Two methods of Looper are called in the main method of the ActivityThread class. We explained prepareMainLooper () earlier. Now let's look at the second method, loop ().


public static void loop() {
    final Looper me = myLooper();// Get Looper Object 
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;// From Looper Object acquisition MessageQueue Object 

    // 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 (;;) {// Infinite loop   1 Straight from MessageQueue Traversing messages in 
        Message msg = queue.next(); // might block
        if (msg == null) {
            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 {
            // Call handler Adj. dispatchMessage Method, giving the message to the handler Deal with 
            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);
        }

        msg.recycleUnchecked();
    }
}

There are many codes for this method. I annotated the code. In fact, it is an infinite loop. 1 will take the message from MessageQueue. If the message is taken, it will execute the code msg. target. dispatchMessage (msg). msg. target is handler, which is actually calling dispatchMessage method of handler, and then passing in message taken from MessageQueue.

Handler dispatchMessage()


public void dispatchMessage(Message msg) {
    // If callback Is not empty, indicating that when sending the message, it is post1 A Runnable Object 
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {// This is used to intercept messages 
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);// Eventually call the handleMessage Method 
    }
}

This method does the final processing of the message, calling the handleCallback method if it is of type post, and if it is a message sent by sendMessage. See if we have intercepted the message, and if not, we will eventually call the handleMessage method to handle it.

Handler handleCallback()

Seeing here, we know why the code executed by post1 Runnable objects and run methods is in the main thread, because the bottom layer does not open the thread at all, just calls run methods.


private static void handleCallback(Message message) {
    message.callback.run();
}

We started with creating handler object, and the whole process of creating Looper and creating MessageQueue. Now let's analyze how to add Message to MessageQueue when we call post and sendMessage methods.

Handler post()

The getPostMessage method is called and Runnable is passed in.


public final boolean post(Runnable r)
{
   return  sendMessageDelayed(getPostMessage(r), 0);
}

Handler getPostMessage()

First, call the Message. obtain () method, take out an Message object, which was mentioned before, and then assign the callback attribute of the Message object to the Runnable object. Seeing this, we also understand why dispatchMessage method should first judge whether callback is empty.


public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}
0

Handler enqueueMessage()

In the post method called sendMessageDelayed method, in fact, the final call is enqueueMessage method, so I look directly here enqueueMessage method source code. Line 1 assigns handler itself to the target property of the messgae object. Then call the enqueueMessage method of MessageQueue to add the current Messgae.


public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}
1

Summarize

Summary: handler is responsible for sending messages, and Looper is responsible for receiving messages sent by Handler and directly passing them back to Handler itself. MessageQueue is a container for storing messages.


Related articles: