Android訊息處理機制(Handler、Looper、MessageQueue與Message)

AngelDevil發表於2013-09-29

Android是訊息驅動的,實現訊息驅動有幾個要素:

  1. 訊息的表示:Message
  2. 訊息佇列:MessageQueue
  3. 訊息迴圈,用於迴圈取出訊息進行處理:Looper
  4. 訊息處理,訊息迴圈從訊息佇列中取出訊息後要對訊息進行處理:Handler

平時我們最常使用的就是Message與Handler了,如果使用過HandlerThread或者自己實現類似HandlerThread的東西可能還會接觸到Looper,而MessageQueue是Looper內部使用的,對於標準的SDK,我們是無法例項化並使用的(建構函式是包可見性)。

我們平時接觸到的Looper、Message、Handler都是用JAVA實現的,Android做為基於Linux的系統,底層用C、C++實現的,而且還有NDK的存在,訊息驅動的模型怎麼可能只存在於JAVA層,實際上,在Native層存在與Java層對應的類如Looper、MessageQueue等。

 初始化訊息佇列

首先來看一下如果一個執行緒想實現訊息迴圈應該怎麼做,以HandlerThread為例:

public void run() {
    mTid = Process.myTid();
    Looper.prepare();
    synchronized (this) {
        mLooper = Looper.myLooper();
        notifyAll();
    }
    Process.setThreadPriority(mPriority);
    onLooperPrepared();
    Looper.loop();
    mTid = -1;
} 

主要是紅色標明的兩句,首先呼叫prepare初始化MessageQueue與Looper,然後呼叫loop進入訊息迴圈。先看一下Looper.prepare。

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

過載函式,quitAllowed預設為true,從名字可以看出來就是訊息迴圈是否可以退出,預設是可退出的,Main執行緒(UI執行緒)初始化訊息迴圈時會呼叫prepareMainLooper,傳進去的是false。使用了ThreadLocal,每個執行緒可以初始化一個Looper。

再來看一下Looper在初始化時都做了什麼:

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

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

在Looper初始化時,新建了一個MessageQueue的物件儲存了在成員mQueue中。MessageQueue的建構函式是包可見性,所以我們是無法直接使用的,在MessageQueue初始化的時候呼叫了nativeInit,這是一個Native方法:

static void android_os_MessageQueue_nativeInit(JNIEnv* env, jobject obj) {
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    if (!nativeMessageQueue) {
        jniThrowRuntimeException(env, "Unable to allocate native queue");
        return;
    }

    nativeMessageQueue->incStrong(env);
    android_os_MessageQueue_setNativeMessageQueue(env, obj, nativeMessageQueue);
}

static void android_os_MessageQueue_setNativeMessageQueue(JNIEnv* env, jobject messageQueueObj,
        NativeMessageQueue* nativeMessageQueue) {
    env->SetIntField(messageQueueObj, gMessageQueueClassInfo.mPtr,
             reinterpret_cast<jint>(nativeMessageQueue));
}

在nativeInit中,new了一個Native層的MessageQueue的物件,並將其地址儲存在了Java層MessageQueue的成員mPtr中,Android中有好多這樣的實現,一個類在Java層與Native層都有實現,透過JNI的GetFieldID與SetIntField把Native層的類的例項地址儲存到Java層類的例項的mPtr成員中,比如Parcel。

再看NativeMessageQueue的實現:

NativeMessageQueue::NativeMessageQueue() : mInCallback(false), mExceptionObj(NULL) {
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
        mLooper = new Looper(false);
        Looper::setForThread(mLooper);
    }
}

在NativeMessageQueue的建構函式中獲得了一個Native層的Looper物件,Native層的Looper也使用了執行緒本地儲存,注意new Looper時傳入了引數false。

Looper::Looper(bool allowNonCallbacks) :
        mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
        mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
    int wakeFds[2];
    int result = pipe(wakeFds);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe.  errno=%d", errno);

    mWakeReadPipeFd = wakeFds[0];
    mWakeWritePipeFd = wakeFds[1];

    result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking.  errno=%d",
            errno);

    result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking.  errno=%d",
            errno);

    // Allocate the epoll instance and register the wake pipe.
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance.  errno=%d", errno);

    struct epoll_event eventItem;
    memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
    eventItem.events = EPOLLIN;
    eventItem.data.fd = mWakeReadPipeFd;
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance.  errno=%d",
            errno);
}

Native層的Looper使用了epoll。初始化了一個管道,用mWakeWritePipeFd與mWakeReadPipeFd分別儲存了管道的寫端與讀端,並監聽了讀端的EPOLLIN事件。注意下初始化列表的值,mAllowNonCallbacks的值為false。

mAllowNonCallback是做什麼的?使用epoll僅為了監聽mWakeReadPipeFd的事件?其實Native Looper不僅可以監聽這一個描述符,Looper還提供了addFd方法:

int addFd(int fd, int ident, int events, ALooper_callbackFunc callback, void* data);
int addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data);

fd表示要監聽的描述符。ident表示要監聽的事件的標識,值必須>=0或者為ALOOPER_POLL_CALLBACK(-2),event表示要監聽的事件,callback是事件發生時的回撥函式,mAllowNonCallbacks的作用就在於此,當mAllowNonCallbacks為true時允許callback為NULL,在pollOnce中ident作為結果返回,否則不允許callback為空,當callback不為NULL時,ident的值會被忽略。還是直接看程式碼方便理解:

int Looper::addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data) {
#if DEBUG_CALLBACKS
    ALOGD("%p ~ addFd - fd=%d, ident=%d, events=0x%x, callback=%p, data=%p", this, fd, ident,
            events, callback.get(), data);
#endif
    if (!callback.get()) {
        if (! mAllowNonCallbacks) {
            ALOGE("Invalid attempt to set NULL callback but not allowed for this looper.");
            return -1;
        }
        if (ident < 0) {
            ALOGE("Invalid attempt to set NULL callback with ident < 0.");
            return -1;
        }
    } else {
        ident = ALOOPER_POLL_CALLBACK;
    }

    int epollEvents = 0;
    if (events & ALOOPER_EVENT_INPUT) epollEvents |= EPOLLIN;
    if (events & ALOOPER_EVENT_OUTPUT) epollEvents |= EPOLLOUT;

    { // acquire lock
        AutoMutex _l(mLock);

        Request request;
        request.fd = fd;
        request.ident = ident;
        request.callback = callback;
        request.data = data;

        struct epoll_event eventItem;
        memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
        eventItem.events = epollEvents;
        eventItem.data.fd = fd;

        ssize_t requestIndex = mRequests.indexOfKey(fd);
        if (requestIndex < 0) {
            int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem);
            if (epollResult < 0) {
                ALOGE("Error adding epoll events for fd %d, errno=%d", fd, errno);
                return -1;
            }
            mRequests.add(fd, request);
        } else {
            int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_MOD, fd, & eventItem);
            if (epollResult < 0) {
                ALOGE("Error modifying epoll events for fd %d, errno=%d", fd, errno);
                return -1;
            }
            mRequests.replaceValueAt(requestIndex, request);
        }
    } // release lock
    return 1;
}

如果callback為空會檢查mAllowNonCallbacks看是否允許callback為空,如果允許callback為空還會檢測ident是否>=0。如果callback不為空會把ident的值賦值為ALOOPER_POLL_CALLBACK,不管傳進來的是什麼值。

接下來把傳進來的引數值封裝到一個Request結構體中,並以描述符為鍵儲存到一個KeyedVector mRequests中,然後透過epoll_ctl新增或替換(如果這個描述符之前有呼叫addFD新增監聽)對這個描述符事件的監聽。

類圖:

  

傳送訊息

透過Looper.prepare初始化好訊息佇列後就可以呼叫Looper.loop進入訊息迴圈了,然後我們就可以向訊息佇列傳送訊息,訊息迴圈就會取出訊息進行處理,在看訊息處理之前,先看一下訊息是怎麼被新增到訊息佇列的。

在Java層,Message類表示一個訊息物件,要傳送訊息首先就要先獲得一個訊息物件,Message類的建構函式是public的,但是不建議直接new Message,Message內部儲存了一個快取的訊息池,我們可以用obtain從快取池獲得一個訊息,Message使用完後系統會呼叫recycle回收,如果自己new很多Message,每次使用完後系統放入快取池,會佔用很多記憶體的,如下所示:

    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

    public void recycle() {
        clearForRecycle();

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

Message內部透過next成員實現了一個連結串列,這樣sPool就了為了一個Messages的快取連結串列。

訊息物件獲取到了怎麼傳送呢,大家都知道是透過Handler的post、sendMessage等方法,其實這些方法最終都是呼叫的同一個方法sendMessageAtTime:

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

sendMessageAtTime獲取到訊息佇列然後呼叫enqueueMessage方法,訊息佇列mQueue是從與Handler關聯的Looper獲得的。

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

enqueueMessage將message的target設定為當前的handler,然後呼叫MessageQueue的enqueueMessage,在呼叫queue.enqueueMessage之前判斷了mAsynchronous,從名字看是非同步訊息的意思,要明白Asynchronous的作用,需要先了解一個概念Barrier。

Barrier與Asynchronous Message

Barrier是什麼意思呢,從名字看是一個攔截器,在這個攔截器後面的訊息都暫時無法執行,直到這個攔截器被移除了,MessageQueue有一個函式叫enqueueSyncBarier可以新增一個Barrier。

    int enqueueSyncBarrier(long when) {
        // Enqueue a new sync barrier token.
        // We don't need to wake the queue because the purpose of a barrier is to stall it.
        synchronized (this) {
            final int token = mNextBarrierToken++;
            final Message msg = Message.obtain();
            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;
        }
    }

在enqueueSyncBarrier中,obtain了一個Message,並設定msg.arg1=token,token僅是一個每次呼叫enqueueSyncBarrier時自增的int值,目的是每次呼叫enqueueSyncBarrier時返回唯一的一個token,這個Message同樣需要設定執行時間,然後插入到訊息佇列,特殊的是這個Message沒有設定target,即msg.target為null。

進入訊息迴圈後會不停地從MessageQueue中取訊息執行,呼叫的是MessageQueue的next函式,其中有這麼一段:

Message msg = mMessages;
if (msg != null && msg.target == null) {
    // Stalled by a barrier.  Find the next asynchronous message in the queue.
    do {
        prevMsg = msg;
        msg = msg.next;
    } while (msg != null && !msg.isAsynchronous());
}

如果佇列頭部的訊息的target為null就表示它是個Barrier,因為只有兩種方法往mMessages中新增訊息,一種是enqueueMessage,另一種是enqueueBarrier,而enqueueMessage中如果mst.target為null是直接拋異常的,後面會看到。

所謂的非同步訊息其實就是這樣的,我們可以透過enqueueBarrier往訊息佇列中插入一個Barrier,那麼佇列中執行時間在這個Barrier以後的同步訊息都會被這個Barrier攔截住無法執行,直到我們呼叫removeBarrier移除了這個Barrier,而非同步訊息則沒有影響,訊息預設就是同步訊息,除非我們呼叫了Message的setAsynchronous,這個方法是隱藏的。只有在初始化Handler時透過引數指定往這個Handler傳送的訊息都是非同步的,這樣在Handler的enqueueMessage中就會呼叫Message的setAsynchronous設定訊息是非同步的,從上面Handler.enqueueMessage的程式碼中可以看到。

 所謂非同步訊息,其實只有一個作用,就是在設定Barrier時仍可以不受Barrier的影響被正常處理,如果沒有設定Barrier,非同步訊息就與同步訊息沒有區別,可以透過removeSyncBarrier移除Barrier:

void removeSyncBarrier(int token) {
    // Remove a sync barrier token from the queue.
    // If the queue is no longer stalled by a barrier then wake it.
    final boolean needWake;
    synchronized (this) {
        Message prev = null;
        Message p = mMessages;
        while (p != null && (p.target != null || p.arg1 != token)) {
            prev = p;
            p = p.next;
        }
        if (p == null) {
            throw new IllegalStateException("The specified message queue synchronization "
                    + " barrier token has not been posted or has already been removed.");
        }
        if (prev != null) {
            prev.next = p.next;
            needWake = false;
        } else {
            mMessages = p.next;
            needWake = mMessages == null || mMessages.target != null;
        }
        p.recycle();
    }
    if (needWake) {
        nativeWake(mPtr);
    }
}

引數token就是enqueueSyncBarrier的返回值,如果沒有呼叫指定的token不存在是會拋異常的。

enqueueMessage

接下來看一下是怎麼MessageQueue的enqueueMessage。

    final boolean enqueueMessage(Message msg, long when) {
        if (msg.isInUse()) {
            throw new AndroidRuntimeException(msg + " This message is already in use.");
        }
        if (msg.target == null) {
            throw new AndroidRuntimeException("Message must have a target.");
        }

        boolean needWake;
        synchronized (this) {
            if (mQuiting) {
                RuntimeException e = new RuntimeException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w("MessageQueue", e.getMessage(), e);
                return false;
            }

            msg.when = when;
            Message p = mMessages;
            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;
            }
        }
        if (needWake) {
            nativeWake(mPtr);
        }
        return true;
    }

注意上面程式碼紅色的部分,當msg.target為null時是直接拋異常的。

在enqueueMessage中首先判斷,如果當前的訊息佇列為空,或者新新增的訊息的執行時間when是0,或者新新增的訊息的執行時間比訊息佇列頭的訊息的執行時間還早,就把訊息新增到訊息佇列頭(訊息佇列按時間排序),否則就要找到合適的位置將當前訊息新增到訊息佇列。

Native傳送訊息

訊息模型不只是Java層用的,Native層也可以用,前面也看到了訊息佇列初始化時也同時初始化了Native層的Looper與NativeMessageQueue,所以Native層應該也是可以傳送訊息的。與Java層不同的是,Native層是透過Looper發訊息的,同樣所有的傳送方法最終是呼叫sendMessageAtTime:

void Looper::sendMessageAtTime(nsecs_t uptime, const sp<MessageHandler>& handler,
        const Message& message) {
#if DEBUG_CALLBACKS
    ALOGD("%p ~ sendMessageAtTime - uptime=%lld, handler=%p, what=%d",
            this, uptime, handler.get(), message.what);
#endif

    size_t i = 0;
    { // acquire lock
        AutoMutex _l(mLock);

        size_t messageCount = mMessageEnvelopes.size();
        while (i < messageCount && uptime >= mMessageEnvelopes.itemAt(i).uptime) {
            i += 1;
        }

        MessageEnvelope messageEnvelope(uptime, handler, message);
        mMessageEnvelopes.insertAt(messageEnvelope, i, 1);

        // Optimization: If the Looper is currently sending a message, then we can skip
        // the call to wake() because the next thing the Looper will do after processing
        // messages is to decide when the next wakeup time should be.  In fact, it does
        // not even matter whether this code is running on the Looper thread.
        if (mSendingMessage) {
            return;
        }
    } // release lock

    // Wake the poll loop only when we enqueue a new message at the head.
    if (i == 0) {
        wake();
    }
}

 Native Message只有一個int型的what欄位用來區分不同的訊息,sendMessageAtTime指定了Message,Message要執行的時間when,與處理這個訊息的Handler:MessageHandler,然後用MessageEnvelope封裝了time, MessageHandler與Message,Native層發的訊息都儲存到了mMessageEnvelopes中,mMessageEnvelopes是一個Vector<MessageEnvelope>。Native層訊息同樣是按時間排序,與Java層的訊息分別儲存在兩個佇列裡。

訊息迴圈

訊息佇列初始化好了,也知道怎麼發訊息了,下面就是怎麼處理訊息了,看Handler.loop函式:

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

loop每次從MessageQueue取出一個Message,呼叫msg.target.dispatchMessage(msg),target就是傳送message時跟message關聯的handler,這樣就呼叫到了熟悉的dispatchMessage,Message被處理後會被recycle。當queue.next返回null時會退出訊息迴圈,接下來就看一下MessageQueue.next是怎麼取出訊息的,又會在什麼時候返回null。

final Message next() {
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;

        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            nativePollOnce(mPtr, nextPollTimeoutMillis);

            synchronized (this) {
                if (mQuiting) {
                    return null;
                }

                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (false) Log.v("MessageQueue", "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf("MessageQueue", "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }

MessageQueue.next首先會呼叫nativePollOnce,然後如果mQuiting為true就返回null,Looper就會退出訊息迴圈。

接下來取訊息佇列頭部的訊息,如果頭部訊息是Barrier(target==null)就往後遍歷找到第一個非同步訊息,接下來檢測獲取到的訊息(訊息佇列頭部的訊息或者第一個非同步訊息),如果為null表示沒有訊息要執行,設定nextPollTimeoutMillis = -1;否則檢測這個訊息要執行的時間,如果到執行時間了就將這個訊息markInUse並從訊息佇列移除,然後從next返回到loop;否則設定nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE),即距離最近要執行的訊息還需要多久,無論是當前訊息佇列沒有訊息可以執行(設定了Barrier並且沒有非同步訊息或訊息佇列為空)還是佇列頭部的訊息未到執行時間,都會執行後面的程式碼,看有沒有設定IdleHandler,如果有就執行IdleHandler,當IdleHandler被執行之後會設定nextPollTimeoutMillis = 0。

首先看一下nativePollOnce,native方法,呼叫JNI,最後調到了Native Looper::pollOnce,並從Java層傳進去了nextPollTimeMillis,即Java層的訊息佇列中執行時間最近的訊息還要多久到執行時間。

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    for (;;) {
        while (mResponseIndex < mResponses.size()) {
            const Response& response = mResponses.itemAt(mResponseIndex++);
            int ident = response.request.ident;
            if (ident >= 0) {
                int fd = response.request.fd;
                int events = response.events;
                void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE
                ALOGD("%p ~ pollOnce - returning signalled identifier %d: "
                        "fd=%d, events=0x%x, data=%p",
                        this, ident, fd, events, data);
#endif
                if (outFd != NULL) *outFd = fd;
                if (outEvents != NULL) *outEvents = events;
                if (outData != NULL) *outData = data;
                return ident;
            }
        }

        if (result != 0) {
#if DEBUG_POLL_AND_WAKE
            ALOGD("%p ~ pollOnce - returning result %d", this, result);
#endif
            if (outFd != NULL) *outFd = 0;
            if (outEvents != NULL) *outEvents = 0;
            if (outData != NULL) *outData = NULL;
            return result;
        }

        result = pollInner(timeoutMillis);
    }
}

先不看開始的一大串程式碼,先看一下pollInner:

int Looper::pollInner(int timeoutMillis) {
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ pollOnce - waiting: timeoutMillis=%d", this, timeoutMillis);
#endif

    // Adjust the timeout based on when the next message is due.
    if (timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        int messageTimeoutMillis = toMillisecondTimeoutDelay(now, mNextMessageUptime);
        if (messageTimeoutMillis >= 0
                && (timeoutMillis < 0 || messageTimeoutMillis < timeoutMillis)) {
            timeoutMillis = messageTimeoutMillis;
        }
#if DEBUG_POLL_AND_WAKE
        ALOGD("%p ~ pollOnce - next message in %lldns, adjusted timeout: timeoutMillis=%d",
                this, mNextMessageUptime - now, timeoutMillis);
#endif
    }

    // Poll.
    int result = ALOOPER_POLL_WAKE;
    mResponses.clear();
    mResponseIndex = 0;

    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

    // Acquire lock.
    mLock.lock();

    // Check for poll error.
    if (eventCount < 0) {
        if (errno == EINTR) {
            goto Done;
        }
        ALOGW("Poll failed with an unexpected error, errno=%d", errno);
        result = ALOOPER_POLL_ERROR;
        goto Done;
    }

    // Check for poll timeout.
    if (eventCount == 0) {
#if DEBUG_POLL_AND_WAKE
        ALOGD("%p ~ pollOnce - timeout", this);
#endif
        result = ALOOPER_POLL_TIMEOUT;
        goto Done;
    }

    // Handle all events.
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ pollOnce - handling events from %d fds", this, eventCount);
#endif

    for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;
        if (fd == mWakeReadPipeFd) {
            if (epollEvents & EPOLLIN) {
                awoken();
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on wake read pipe.", epollEvents);
            }
        } else {
            ssize_t requestIndex = mRequests.indexOfKey(fd);
            if (requestIndex >= 0) {
                int events = 0;
                if (epollEvents & EPOLLIN) events |= ALOOPER_EVENT_INPUT;
                if (epollEvents & EPOLLOUT) events |= ALOOPER_EVENT_OUTPUT;
                if (epollEvents & EPOLLERR) events |= ALOOPER_EVENT_ERROR;
                if (epollEvents & EPOLLHUP) events |= ALOOPER_EVENT_HANGUP;
                pushResponse(events, mRequests.valueAt(requestIndex));
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on fd %d that is "
                        "no longer registered.", epollEvents, fd);
            }
        }
    }
Done: ;

    // Invoke pending message callbacks.
    mNextMessageUptime = LLONG_MAX;
    while (mMessageEnvelopes.size() != 0) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);
        if (messageEnvelope.uptime <= now) {
            // Remove the envelope from the list.
            // We keep a strong reference to the handler until the call to handleMessage
            // finishes.  Then we drop it so that the handler can be deleted *before*
            // we reacquire our lock.
            { // obtain handler
                sp<MessageHandler> handler = messageEnvelope.handler;
                Message message = messageEnvelope.message;
                mMessageEnvelopes.removeAt(0);
                mSendingMessage = true;
                mLock.unlock();

#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
                ALOGD("%p ~ pollOnce - sending message: handler=%p, what=%d",
                        this, handler.get(), message.what);
#endif
                handler->handleMessage(message);
            } // release handler

            mLock.lock();
            mSendingMessage = false;
            result = ALOOPER_POLL_CALLBACK;
        } else {
            // The last message left at the head of the queue determines the next wakeup time.
            mNextMessageUptime = messageEnvelope.uptime;
            break;
        }
    }

    // Release lock.
    mLock.unlock();

    // Invoke all response callbacks.
    for (size_t i = 0; i < mResponses.size(); i++) {
        Response& response = mResponses.editItemAt(i);
        if (response.request.ident == ALOOPER_POLL_CALLBACK) {
            int fd = response.request.fd;
            int events = response.events;
            void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
            ALOGD("%p ~ pollOnce - invoking fd event callback %p: fd=%d, events=0x%x, data=%p",
                    this, response.request.callback.get(), fd, events, data);
#endif
            int callbackResult = response.request.callback->handleEvent(fd, events, data);
            if (callbackResult == 0) {
                removeFd(fd);
            }
            // Clear the callback reference in the response structure promptly because we
            // will not clear the response vector itself until the next poll.
            response.request.callback.clear();
            result = ALOOPER_POLL_CALLBACK;
        }
    }
    return result;
}

Java層的訊息都儲存在了Java層MessageQueue的成員mMessages中,Native層的訊息都儲存在了Native Looper的mMessageEnvelopes中,這就可以說有兩個訊息佇列,而且都是按時間排列的。timeOutMillis表示Java層下個要執行的訊息還要多久執行,mNextMessageUpdate表示Native層下個要執行的訊息還要多久執行,如果timeOutMillis為0,epoll_wait不設定TimeOut直接返回;如果為-1說明Java層無訊息直接用Native的time out;否則pollInner取這兩個中的最小值作為timeOut呼叫epoll_wait。當epoll_wait返回時就可能有以下幾種情況:

  1. 出錯返回。

  2. Time Out

  3. 正常返回,描述符上有事件產生。

如果是前兩種情況直接goto DONE。

否則就說明FD上有事件發生了,如果是mWakeReadPipeFd的EPOLLIN事件就呼叫awoken,如果不是mWakeReadPipeFd,那就是透過addFD新增的fd,在addFD中將要監聽的fd及其events,callback,data封裝成了Request物件,並以fd為鍵儲存到了KeyedVector mRequests中,所以在這裡就以fd為鍵獲得在addFD時關聯的Request,並連同events透過pushResonse加入mResonse佇列(Vector),Resonse僅是對events與Request的封裝。如果是epoll_wait出錯或timeout,就沒有描述符上有事件,就不用執行這一段程式碼,所以直接goto DONE了。

void Looper::pushResponse(int events, const Request& request) {
    Response response;
    response.events = events;
    response.request = request;
    mResponses.push(response);
}

接下來進入DONE部分,從mMessageEnvelopes取出頭部的Native訊息,如果到達了執行時間就呼叫它內部儲存的MessageeHandler的handleMessage處理並從Native 訊息佇列移除,設定result為ALOOPER_POLL_CALLBACK,否則計算mNextMessageUptime表示Native訊息佇列下一次訊息要執行的時間。如果未到頭部訊息的執行時間有可能是Java層訊息佇列訊息的執行時間小於Native層訊息佇列頭部訊息的執行時間,到達了Java層訊息的執行時間epoll_wait TimeOut返回了,或都透過addFd新增的描述符上有事件發生導致epoll_wait返回,或者epoll_wait是出錯返回。Native訊息是沒有Barrier與Asynchronous的。

最後,遍歷mResponses(前面剛透過pushResponse存進去的),如果response.request.ident == ALOOPER_POLL_CALLBACK,就呼叫註冊的callback的handleEvent(fd, events, data)進行處理,然後從mResonses佇列中移除,這次遍歷完之後,mResponses中保留來來的就都是ident>=0並且callback為NULL的了。在NativeMessageQueue初始化Looper時傳入了mAllowNonCallbacks為false,所以這次處理完後mResponses一定為空。

接下來返回到pollOnce。pollOnce是一個for迴圈,pollInner中處理了所有response.request.ident==ALOOPER_POLL_CALLBACK的Response,在第二次進入for迴圈後如果mResponses不為空就可以找到ident>0的Response,將其ident作為返回值返回由呼叫pollOnce的函式自己處理,在這裡我們是在NativeMessageQueue中呼叫的Loope的pollOnce,沒對返回值進行處理,而且mAllowNonCallbacks為false也就不可能進入這個迴圈。pollInner返回值不可能是0,或者說只可能是負數,所以pollOnce中的for迴圈只會執行兩次,在第二次就返回了。

Native Looper可以單獨使用,也有一個prepare函式,這時mAllowNonCallbakcs值可能為true,pollOnce中對mResponses的處理就有意義了。

 wake與awoken

在Native Looper的建構函式中,透過pipe開啟了一個管道,並用mWakeReadPipeFd與mWakeWritePipeFd分別儲存了管道的讀端與寫端,然後用epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd,& eventItem)監聽了讀端的EPOLLIN事件,在pollInner中透過epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis)讀取事件,那是在什麼時候往mWakeWritePipeFd寫,又是在什麼時候讀的mWakeReadPipeFd呢?

在Looper.cpp中我們可以發現如下兩個函式:

void Looper::wake() {
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ wake", this);
#endif

    ssize_t nWrite;
    do {
        nWrite = write(mWakeWritePipeFd, "W", 1);
    } while (nWrite == -1 && errno == EINTR);

    if (nWrite != 1) {
        if (errno != EAGAIN) {
            ALOGW("Could not write wake signal, errno=%d", errno);
        }
    }
}

void Looper::awoken() {
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ awoken", this);
#endif

    char buffer[16];
    ssize_t nRead;
    do {
        nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));
    } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));
}

wake函式向mWakeWritePipeFd寫入了一個“W”字元,awoken從mWakeReadPipeFd讀,往mWakeWritePipeFd寫資料只是為了在pollInner中的epoll_wait可以監聽到事件返回。在pollInner也可以看到如果是mWakeReadPipeFd的EPOLLIN事件只是呼叫了awoken消耗掉了寫入的字元就往後處理了。

那什麼時候呼叫wake呢?這個只要找到呼叫的地方分析一下就行了,先看Looper.cpp,在sendMessageAtTime即傳送Native Message的時候,根據傳送的Message的執行時間查詢mMessageEnvelopes計算應該插入的位置,如果是在頭部插入,就呼叫wake喚醒epoll_wait,因為在進入pollInner時根據Java層訊息佇列頭部訊息的執行時間與Native層訊息佇列頭部訊息的執行時間計算出了一個timeout,如果這個新訊息是在頭部插入,說明執行時間至少在上述兩個訊息中的一個之前,所以應該喚醒epoll_wait,epoll_wait返回後,檢查Native訊息佇列,看頭部訊息即剛插入的訊息是否到執行時間了,到了就執行,否則就可能需要設定新的timeout。同樣在Java層的MessageQueue中,有一個函式nativeWake也同樣可以透過JNI呼叫wake,呼叫nativeWake的時機與在Native呼叫wake的時機類似,在訊息佇列頭部插入訊息,還有一種情況就是,訊息佇列頭部是一個Barrier,而且插入的訊息是第一個非同步訊息。

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();//如果頭部是Barrier並且新訊息是非同步訊息則“有可能”需要喚醒
    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;
}

在頭部插入訊息不一定呼叫nativeWake,因為之前可能正在執行IdleHandler,如果執行了IdleHandler,就在IdleHandler執行後把nextPollTimeoutMillis設定為0,下次進入for迴圈就用0呼叫nativePollOnce,不需要wake,只有在沒有訊息可以執行(訊息佇列為空或沒到執行時間)並且沒有設定IdleHandler時mBlocked才會為true。

如果Java層的訊息佇列被Barrier Block住了並且當前插入的是一個非同步訊息有可能需要喚醒Looper,因為非同步訊息可以在Barrier下執行,但是這個非同步訊息一定要是執行時間最早的非同步訊息。

退出Looper也需要wake,removeSyncBarrier時也可能需要。

相關文章