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:
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: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(); }
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 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); }
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:public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0);//The getPostMessage method is the difference between the two methods of sending messages }
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.private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }
Look again at the enqueueMessage method:
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: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 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):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 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:
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.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(); }