handler mechanism of Android asynchronous message processing mechanism

Posted by mrbippy on Tue, 09 Jul 2019 22:03:58 +0200

In the past, we analyzed looper, and realized updating ui by rewriting handleMessage in main thread (see blogger's first two blogs specifically). Next, we mainly analyzed handler and its post(runnable r) method.

First, let's look at the asynchronous updating ui in the post version, in Activity:

private Handler mHandler;//global variable
@Override
protected void onCreate(Bundle savedInstanceState) {
    mHandler = new Handler();
    new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
         Thread.sleep(1000);//There is a time-consuming operation in the sub-thread, such as requesting the network
                        mHandler.post(new Runnable() {
                            @Override
                            public void run() {
                         mTestTV.setText("This is post");//Update UI
                            }
                        });
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
}
On the surface, a Runnable is passed to the post method, which is like opening a sub-thread, but the UI can not be updated in the sub-thread. So the problem arises. What is the situation? With this doubt, to flip through the source code of Handler, let's first look at the final implementation of sendMessage AtTime, such as sendMessage:
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);
    }
enqueueMessage is a method of adding messages to a specified message queue. Next, let's look at the source code of the post method.
public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);//The getPostMessage method is the difference between the two methods of sending messages
    }
There is only one sentence in the method, and the internal implementation is the same as the normal sendMessage, but there is only one difference, that is getPostMessage(r) method:
private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }
In this way, we find that we also encapsulate the parameters we passed in into a Message, but this time it's m.callback = r, just msg.what=what, but we don't see the attributes of the Message. Seeing here, we just know that the post and sendMessage principles are encapsulated as Messages, but we still don't know the whole of Handler. Continue to explore what the mechanism looks like.

Look again at the enqueueMessage method:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
Regardless of the asynchronous relationship, mAsynchronous continues to pass parameters to queue's enqueueMessage method. As for the assignment of the msg's target, let's see later. Now we continue to enter the enqueueMessage method of MessageQueue class. The method is longer. Let's look at the key lines:
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;
    }
It can be seen that the insertion operation of single linked list is actually the insertion operation, and the insertion order is sorted according to the time when, ranking the small time first, the big time behind, and what is the target variable appearing in Message? Looking at the source code shows that the target is actually Handler. msg.target = this; binds the message queue to the current handler again, so let's look at dispatchMessage in Handler, which implements message distribution (specifically described in Looper, so it's no longer described in detail):
The call back of msg should have thought about what is the run method of Runnable that we passed in through Handler.post(Runnable r). Here we will mention the Java basis. The run method of calling threads directly is equivalent to calling methods in a common class or executing in the current thread, and it will not open new threads.
So, in the end, to answer our initial question, why can we update ui in new Thread by post ing, for example:
private Handler mHandler;//global variable
@Override
protected void onCreate(Bundle savedInstanceState) {
    mHandler = new Handler();
    new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);//There is a time-consuming operation in the sub-thread, such as requesting the network
                        mHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                mTestTV.setText("This is post");//Update UI
                            }
                        });
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
}
This is because mHandler is created in the main thread, and mHandler.post(new Runnable() binds messages to handler through targer, and run() does not have the function of opening new threads, so we can still update ui.

  

 


Topics: network Java