Analyzing Handler s with source code can also be asynchronous.First, the following classes of Android message processing mechanisms are introduced:
1, Message: Message entity;
2, MessageQueue: Message Queue;
3, Looper: message loop body;
4, Handler: Message processing;
Mechanisms for message processing, starting with Looper.prepare(), Looper.loop(), look at the source code for Looper.loop():
public static void loop() { //Omit Code final MessageQueue queue = me.mQueue; for (;;) { // Get a Message from MessageQueue, queue.next() will block the cpu Message msg = queue.next(); if (msg == null) { // No message indicates that the message queue is quitting. return; } //Omit Code // msg.target is actually a Handler instance, dispatchMessage handles messages msg.target.dispatchMessage(msg); //Omit Code msg.recycleUnchecked(); } }
There are two key codes: a queue.next(), an msg.target.dispatchMessage(msg), and the Handler.dispatchMessage() method:
public void dispatchMessage(Message msg) { if (msg.callback != null) { //If Message.callback is not empty, that is, there is Runnable, then its run method will run directly; handleCallback(msg); } else { if (mCallback != null) { // Callback.handleMessage is called if a Callback is passed when a new Handler if (mCallback.handleMessage(msg)) { return; } } //Message has no Runnable, Handler has no Callback, or Callback.handleMessage returns false before calling Handler's own handleMessage handleMessage(msg); } }
Next, look at the MessageQueue.next() method:
Message next() { for (;;) { //Omit Code // @1 // native layer blocking cpu nativePollOnce(ptr, nextPollTimeoutMillis); //Omit Code synchronized (this) { final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { // @2 // If msg.target is empty, only MessageQueue.enqueueSyncBarrier // msg.isAsynchronous can only be used in conjunction with Barrier, which means that even if there are asynchronous messages, it will not work if Barrier is not set. do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now < msg.when) { // @3 //There's a message, but it's not time yet to go ahead and block the CPU (note @1 above) nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { mBlocked = false; if (prevMsg != null) { // @4 // prevMsg is not empty and mMessages will not change, so if there is a Barrier, always try to get asynchronous messages (that is, note @2 above) // So remember to set up Barrier, remove it accordingly, and call MessageQueue.removeSyncBarrier prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; return msg; } } else { nextPollTimeoutMillis = -1; } //Omit Code } // @5 // IdleHandler.queueIdle() is called when the message queue is idle, that is, when there is a message but it is not time, or when there is no message. // Can be added through the MessageQueue.addIdleHandler method for (int i = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; boolean keep = false; try { keep = idler.queueIdle(); } catch (Throwable t) { } if (!keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } //Omit Code } }
Why is there only MessageQueue.enqueueSyncBarrier when msg.target is empty?
Because normally Handler's sendMessage or Message's sendToTarget actually ends up walking to
MessageQueue.enqueueMessage:
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("MessageQueue", 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; }
As you can see from the code above, a normal sendMessage throws an exception if the target is empty.
Instead, MessageQueue.enqueueSyncBarrier:
int enqueueSyncBarrier(long when) { synchronized (this) { final int token = mNextBarrierToken++; final Message msg = Message.obtain(); msg.markInUse(); msg.when = when; msg.arg1 = token; Message prev = null; Message p = mMessages; if (when != 0) { while (p != null && p.when <= when) { prev = p; p = p.next; } } if (prev != null) { // invariant: p == prev.next msg.next = p; prev.next = msg; } else { msg.next = p; mMessages = msg; } return token; } }
From the code above, you can see that Barrier is also a Message and does not have a target.
How do I make msg.isAsynchronous?
There are two ways:
1, the Message method setAsynchronous();
2, the construction method of Handler, which can pass in the async parameter;
public Handler(Handler.Callback callback, boolean async) { //Omit Code mLooper = Looper.myLooper(); mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { // Handler's sendMessage assigns msg to Asynchronous msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
Summary:
1. Message queue loops, not necessarily executed entirely in time series, can have asynchronous messages.
2. Asynchronous messages must be used in conjunction with Barrier, which means that even if there are asynchronous messages, it will not work if Barrier is not set.
3. Set up Barrier and remove it accordingly, otherwise synchronous messages will never be processed.