This time, we parse the source code of the Android messaging mechanism in the most detailed way.

Handler Source Parsing

1. Creating Handler Objects

The easiest way to use handler: directly new a handler object

Handler handler = new Handler();

So let's look at the source code of its constructor:

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

This code does a few things:

1. Check for possible memory leaks
2. Initialize a Looper mLooper
3. Initialize a MessageQueue mQueue

Let's look at things one by one:

1. Check for memory leaks

Handler's constructor first determines the value of FIND_POTENTIAL_LEAKS. When true, it gets the runtime class of the object. If it is an anonymous class, a member class, and a local class, it determines whether the modifier is static or not. If not, it indicates that memory leaks may occur.

Question: Why can memory leaks occur when the modifiers of anonymous, member and local classes are not static?

Answer: Because anonymous classes, member classes and local classes are all internal classes. Internal classes hold references to external classes. If Activity is destroyed and Hanlder's task is not completed, then Handler holds references to activity, which leads to memory leaks. Static internal classes are static members of external classes and do not hold references to internal classes. Therefore, no memory leak will occur.

Here we can think about why non-static classes hold references to external classes. Why do static classes not hold references to external classes?

Question: How to avoid memory leaks with Handler?
Answer: How to use static internal classes

2. Initialize a Looper mLooper

Here we get an mLooper, and if it's empty, we run out of the exception:

"Can't create handler inside thread that has not called Looper.prepare() "

If Looper.prepare() is not called, handler cannot be created in the thread! We all know that if we create handlers in UI threads, we don't need to call this method, but if we create handlers in other threads, we need to call this method. So what exactly does this approach do? Let's look at the code:

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

If the value of sThreadLocal.get() is taken first, and the result is not empty, then the exception runs out, "Only one Looper can be created in one thread", so the Looper is stored in sThreadLocal; if the result is empty, a Looper is created. Let's look at the code for myLooper():

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

In summary, we came to the conclusion that when we created Handler in the UI thread, there was already a Looper object in sThreadLocal, so there was a question:
Where did Looper in sThreadLocal come from when we created Handler in the UI thread?
We know that we need to call the getMainLooper() method to get the Looper of the main thread. The code is as follows:

public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }

So let's trace the assignment of this variable and find that there is an assignment in the method prepareMainLooper(). Let's look at the code:

public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
  • The first step is to call prepare(false), which we have just seen is to create a Looper object and store it in sThreadLocal;\
  • Then it judges whether sMainLooper is empty and empty throws an exception.
  • If sMainLooper is not empty, sMainLooper = myLooper()
    So far the sMainLooper object assignment has been successful, so we need to know where the prepareMainLooper() method is called. Following the code, we find that Looper.prepareMainLooper() is called in the main method of ActivityThread. Now the truth is clear:
    When we create Handler in the UI thread, the Looper in sThreadLocal is initialized when the prepareMainLooper() method is called in the main function of ActivityThread.
    ActivityThread is a class that manages the execution of Android main threads in an application process, including activities, broadcasting, and other operations.

3. Initialize a MessageQueue mQueue

From the code, we can see that we call mLooper.mQueue directly to get this object, which may be generated when Looper is initialized. Let's look at Looper's initialization code:

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

The code is simple. It creates the MessageQueue object and gets the current thread.

So far, the creation of Handler has been completed, essentially to get a Looper object and a MessageQueue object!

2. Sending Messages Using Handler

Handler sends messages in many ways. We follow a method sendMessage method all the time and find that enqueueMessage(queue, msg, uptimeMillis) is finally called. Let's look at the code of this method:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

This code does three things:

1. Assignment of msg.target, which is the Handler object
2. Is setting a message asynchronous?
3. Call the enqueueMessage(msg, uptimeMillis) method of MessageQueue
We only focus on the third step: this step transfers the message sent by Handler to the way MessageQueue adds the message.
So far, Handler's task of sending messages has been completed, essentially calling MessageQueue's own method of adding messages!

3. MessageQueue Adding Messages

The constructor code of MessageQueue is as follows:

MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }

Nothing special happened. Let's look at the enqueueMessage (msg, uptime Millis) method code:

boolean enqueueMessage(Message msg, long when) {
        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;
    }

The code is long, but by looking at this code, we find that the MessageQueue is actually a linked list, and the process of adding messages is actually a single linked list insertion process.

So we know that the essence of Handler sending messages is to add messages to MessageQueue, which is actually a single linked list, and the essence of adding messages is the insertion of a single linked list.

4. Getting the message out of the message queue

We already know how messages are stored, and we need to know how messages are retrieved.
So let's look at Looper.loop(); this method:

   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;

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            try {
                msg.target.dispatchMessage(msg);
            }

        }
    }

The code is too long. I deleted some of the code. It can be seen that the main function of this method is very simple.

  • Get the Looper object and throw an exception if it is empty.
  • Get message queue MessageQueue queue
  • Traversal loops fetch messages from message queues and distribute them when the message is empty, the loop ends and the message is not empty.
    But the queue.next() method is actually blocked when there is no message, and the mBlocked is marked as true, and it does not immediately return null. The reason for this method blocking is native PollOnce (ptr, next Poll Timeout Millis); method blocking. Blocking is to wait for news to come. So if a message joins the queue, how does the loop() method continue to cancel the message?
    It depends on what happens when the message joins the queue. Let's look at the enqueueMessage (msg, uptime Millis) method and find out
    if (needWake) {
    nativeWake(mPtr);
    }

    When needWake is called, a local method wakes up to read the message.
    So here's a look at what happened after the message was distributed.

    msg.target.dispatchMessage(msg);

    As mentioned above, this target is actually a handler. So let's take a look at the method code in handler

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

    The code is very simple. Call the handleMessage(msg) method of the callback when the callback is not empty, and call your own handleMessage(msg) when the callback is empty. Normally, instead of passing in callback, we copy handleMessage(msg) method of Handler directly to process our message.

Keywords: Android

Added by keyur.delhi on Fri, 14 Jun 2019 01:30:09 +0300