Simple analysis of Android multi-threading-Handler principle

In the previous chapter, we have already understood the process of using Handler for inter-thread communication. We know that there are several important concepts that we need to understand. They are:

concept definition effect
Main thread (UI Thread) When the application starts, the UI thread is enabled by default Handling UI related operations
Child thread (Worker Thread) Threads opened by human control Handling time-consuming operations (network requests, data loading, etc.)
Message Data carrier for inter-thread communication Stores the information that needs to be passed between threads
Message Queue (MessageQueue) A data structure that stores Messages (first-in, first-out FIFO) Stores messages sent by Handler
Handler Thread communication medium, inter-thread message processor Send messages to message queues and process messages distributed by Looper
Looper Communication bridge between message queue and processor Loop out the message in the message queue and distribute the message to the corresponding handler

Simple analysis

//定义handler
Handler handler = new Handler();
//发送消息
handler.post(new Runnable() {
     @Override
        public void run() {
            //do something
        }
});

First, we open the source code of the parameterless constructor of Handler and call the constructor with two parameters, as follows:

public Handler(Callback callback, boolean async) {
    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;
}

You can see that the looper's myLooper method is called first, and it is judged whether the acquired object is null. Let's continue to look at the Looper code:

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

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

This involves the related content of ThreadLocal. I don't know much about this, so I won't make a fool of myself. I may do research on Java multi-threading in the future. Here we only need to know that if the object returned by myLooper is empty, it means that the The thread does not initialize the Looper, and the handler will report an error, so why do we use the Handler in the main thread without a problem? This is because we look at these codes:

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

When we trace this line of code, we can see that this code is called in the main method of the ActivityThread class, where the Looper of the main thread is bound, which is why we can use the handler normally.

Next, let's look at the second part of the code, which is the code part of handler.post:

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

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}
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);
}

It can be seen that this method actually calls the sendMessage method, but the incoming Runnable object is set as the callback of the Message. It can also be seen from this process that if the Looper of this thread fails to initialize, then the MessageQueue is also empty, and an error will still be reported in the sendMessageAtTime method. Let's take a look at the enqueueMessage method, and see the name to know that it is a method of enqueuing:

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

It can be seen that in this method, the Target property is set to the current Handler for the incoming Message, and the message is put into the message queue according to the message time.

At this point, the message we sent has been put into the message queue, so how to take out the message and send it to the Handler, we continue to open Looper's code and find this line:

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 (;;) {
        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
        Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                            msg.callback + ": " + msg.what);
        }

        msg.target.dispatchMessage(msg);

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

We just looked at the ActivityThread code and found that the Looper.loop method was called in the main method. From the method name, this is a loop, and it is true from the code. There is an infinite loop method, which continuously takes messages from MessageQueue. If the message is empty, it blocks. If the message is not empty, the dispatchMessage method of the target in the Message is called. After consuming the message, it is recycled. We know that this target is also Handler, let's see what this method does:

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

A judgment is made here. When Message has a callback method, use the callback method of Message to process it. If the callback method does not exist, call handleMessage and pass the message into this method for further processing.

In this example, we know that the callback of the message is the runnable we passed in, then the handleCallback method will be executed, that is:

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

If it is the way we wrote in the previous section, the handleMessage method will be called, which is the method we overwrite, and the logic flow written by ourselves will be executed.

At this point, our process analysis is over. Of course, we only explain the Handler from the fur. We summarize here:

The child thread sends a message to the main thread:

  1. Create a Handler in the main thread and overwrite the corresponding method, or use the post (Runnable) method to write down the logic code
  2. Use Handler's sendMessage or post to send messages in child threads
  3. Handler processes messages internally and passes them to MessageQueue, which performs corresponding enqueue operations according to the message execution time
  4. Looper continuously polls to take out messages from MessageQueue. If there is no message, it will block. If there is a message, it will distribute the message to the corresponding Handler.
  5. The Handler receives the distributed message and uses the corresponding method to process it. At this point, the communication between the sub-thread and the main thread ends.

ps: There are a few important additional knowledge:

If the child thread wants to send a message to the child thread, it needs to call Looper.prepare and Looper.loop in the child thread to use Handler, otherwise an error will be reported

There can only be one Looper and one MessageQueue in a thread, but there can be multiple Handlers

Well, this is the content of this chapter. In the next section, we will learn about HandlerThread, which is provided to us to solve multi-threaded communication in Android. Please look forward to it~
my personal blog , welcome to visit~
enjoy~

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325542869&siteId=291194637