Android Handler消息隊列的實現原理

我們在寫Android程序的時候,有經常用到Handler來與子線程通信,亦或者是用其來管理程序運行的狀態時序。Handler其是由Android提供的一套完善的操作消息隊列的API。它既可以運行在主線程中,也可以運行在子線程中,唯一的區別是其內部的Looper對象不同。在這裏我將對Android中Handler消息隊列的實現進行一個總結,以便深入瞭解其原理並且鞏固其使用方式。

本系列的主要內容如下:

  • 1.整體架構與主要的數據結構
  • 2.Message的入列出列以及延遲處理的實現方式
  • 3.底層原理:epoll的基礎概念以及在Handler中的應用

整體架構

實現Handler消息隊列的源碼如下:

framework/base/core/java/android/os/Handler.java
framework/base/core/java/android/os/Looper.java
framework/base/core/java/android/os/Message.java
framework/base/core/java/android/os/MessageQueue.java
framework/base/core/jni/android_os_MessageQueue.cpp
system/core/libutils/Looper.cpp
system/core/include/utils/Looper.h

Handler: 主要提供各類API用於將Message對象加入到隊列中,並在Message從隊列中被取出時,觸發handleMessage回調或者執行Message中的Runable對象的run函數。

Looper: java層的Looper類是消息隊列的工作引擎,提供一個死循環不斷從MessageQueue中取出Message, 取出的Message會在Handler的handleMessage中處理。

Message: 消息隊列中所傳遞的消息的數據類型,其父類是Parcelable。內部還包含了一個Message對象池的實現,用來複用Message對象;

MessageQueue:消息隊列,主要實現Message的入列,出列的邏輯管理,維護Message單鏈表,隊列的清空,以及與JNI部分的通信。

android_os_MessageQueue: 消息隊列的JNI部分實現,內部的主要邏輯就是初始化native的Looper對象,以及在java部分調用JNI中的方法時,執行native Looper部分對應的方法。

native Looper: native層的Looper中實現了兩個功能,一個是利用epoll實現了隊列的阻塞與喚醒功能,另外一個是實現了一套native層中使用的隊列機制。

其分層架構如下所示:Handler, Message,Looper三者圍繞MessageQueue進行處理,MessageQueue通過jni與Looper.cpp進行通信實現空隊列時阻塞,有消息入列時喚醒等功能。

在這裏插入圖片描述
整體架構如下:MessageQueue中維護着一個Message的單鏈表,Handler中enqueueMessage將消息添加到隊列中,Looper.java從隊列中取出Message並將其交由Handler的dispatchMessage分發。android_os_MessageQueue.cpp主要是jni的實現部分,完成MessageQueue跟Looper.cpp的交互。 Looper.cpp則使用epoll機制實現了隊列的阻塞與喚醒。
在這裏插入圖片描述

數據結構

Android的隊列所管理的數據類型爲Message, 消息隊列所用的數據結構則是Message類型的單鏈表。一般模式下其結構如下所示:
在這裏插入圖片描述
在Looper中,依次從Message 0 到 Message n中將數據取出丟給Handler處理。當添加新的數據時,如果Message對象的when屬性爲0,則將其添加到鏈頭。這種情況一般是我們調用Handler的sendMessageAtFrontOfQueue時出現。
在這裏插入圖片描述
在其他情況下,每一次添加新的消息時,都需要從鏈頭依次對比Message的when變量,當新消息的when變量小於鏈中元素的when變量,則將其插入鏈中。一般調用sendEmptyMessageAtTimesendEmptyMessageDelayed sendMessageDelayedsendMessageAtTime等函數時,會出現延遲處理的消息。如果隊列中存在延遲消息,那麼使用sendMessage等及時處理的消息時,會出現其插入在鏈表中的情況。
在這裏插入圖片描述在這裏插入圖片描述
以上總結了隊列的數據結構是一個單鏈表,因此我們上述所有疑問,都可以轉換成如何操作單鏈表的問題。正常情況下的單鏈表如下所示:
在這裏插入圖片描述
那Message究竟是怎麼加入到隊列中,又是怎麼從隊列中取出最終到handlerMessage中爲我們所得, 爲什麼隊列可以將數據優先處理,爲什麼有的數據可以延遲處理,其具體實現隊列管理的算法是怎麼實現的。

首先看一下,Handler是如何從隊列中取值的,具體實現在MessageQueue中的next()函數

//獲取當前時間戳
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
//mMessages是一個全局的Message類型變量,保存着鏈頭的Message, 如果隊列沒有數據,則該變量指向空
Message msg = mMessages; 
if (msg != null && msg.target == null) {
        //無主的Message,不處理,直接尋找下一個節點的Message.
        // 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) {
                  //當前時間還沒達到隊列中第一個Message的消息處理時間,需要繼續等待。
                  // 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;
                  //返回當前鏈頭的Message,並將mMessages對象指向其內部名爲next的Message類型的對象。
                  //這裏就相當於取出了隊列的頭部數據。
                  if (prevMsg != null) {
                       prevMsg.next = msg.next;
                  } else {
                        mMessages = msg.next;
                  }
                  msg.next = null;
                  if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                  //標識當前Message正在使用中
                  msg.markInUse();
                  //返回Message對象。
                  return msg;
        }
} else {
        // No more messages.
        nextPollTimeoutMillis = -1;
}

這部分僅僅列出取值的具體算法,其他包括空隊列的IdleHandler的處理,以及阻塞的邏輯的部分這裏我並沒有貼出來。

Looper調用MessageQueue對象的next()方法獲取到從隊列中返回的Message對象後,再將其交由Handler去處理,也就是走到我們經常用到的handleMessage(Message msg)回調中,這部分的邏輯如下:

Message msg = queue.next(); // might block
if (msg == null) {
       // No message indicates that the message queue is quitting.
       return;
 }
 
......
......

try {
     msg.target.dispatchMessage(msg);
} finally {
     if (traceTag != 0) {
          Trace.traceEnd(traceTag);
     }
}

每一個Message對象都有一個target變量,這個target變量類型就是Handler, 每當Message取出時,都提交到其自身歸屬的Handler去處理。
在這裏插入圖片描述
每次取消息的時序圖如上所示,MessageQueue由Looper初始化,初始化之後,調用其loop方法開始讓隊列開始工作。loop方法中,開始循環調用MessageQueue的next()函數去拿Message數據,next()具有阻塞特性,當隊列沒有消息時,nativePollOnce()函數處會阻塞,原理是底層使用epoll實現了消息的阻塞/喚醒機制。當隊列中有新加入的數據時,epoll_wait就會退出,從而MessageQueue的next()方法將Message返回給Looper,並提交到Handler中消化。

知道了Handler的消息取出的流程,接下來看一下將消息加入隊列的邏輯。Handler一共爲我們提供瞭如下公開的API用來將消息添加到隊列中

//發送一個空消息
 public final boolean sendEmptyMessage(int what)
 //發送一個延遲處理的空消息
 public final boolean sendEmptyMessageDelayed(int what, long delayMillis)
 //發送一個指定時間戳執行的空消息
 public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis)
 //發送一個延遲消息
 public final boolean sendMessageDelayed(Message msg, long delayMillis)
 //發送一個定時消息
 public boolean sendMessageAtTime(Message msg, long uptimeMillis)
 //將消息發送至隊列的頭
 public final boolean sendMessageAtFrontOfQueue(Message msg)
 //提交一個任務
 public final boolean post(Runnable r)
 //提交一個定時任務
 public final boolean postAtTime(Runnable r, long uptimeMillis)
 //提交一個帶身份認證的定時任務
 public final boolean postAtTime(Runnable r, Object token, long uptimeMillis)
 //提交一個延遲任務
 public final boolean postDelayed(Runnable r, long delayMillis)
 //提交一個任務到隊列頭
 public final boolean postAtFrontOfQueue(Runnable r)

以上方法,最終均會調用Handler的私有方法將Message提交到隊列中,uptimeMillis就是該msg最終執行的時間戳,也用來進行鏈中元素的排序。

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

接下來看一下消息插入的具體實現,MessageQueue中的enqueueMessage方法。

boolean enqueueMessage(Message msg, long when) {
      ...
      ...
            //標記當前Message正在被使用
            msg.markInUse();
            //將Message的when變量賦值爲執行時間戳
            msg.when = when;
            //mMessages爲鏈頭,如果此時隊列無數據,則mMessages爲空
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                //這裏判斷有三種情況,一種是鏈頭爲空表示當前隊列無數據,一種是當前提交的Message執行時間戳爲0,表示使用者
                //希望將其加入到隊列頭,還有一種情況是當前提交的Message執行時間戳小於鏈頭Message的執行時間戳,因此也需    
                //要將其加入到隊列頭                
                // New head, wake up the event queue if blocked.
                //加入隊列頭只需要將自身的next變量引用到當前的鏈頭對象,然後再將代表當前鏈頭的mMessages變量引用到msg
                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的插入位置。Message的插入位置
				//是根據其內部的when變量來判斷,當鏈表中的元素when屬性值大於新元素的時間戳值,
				//則將元素加入到該元素的前一個節點。
                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.
            //隊列爲空時,此時epoll_wait處於等待狀態,我們需要將其喚醒,然後loop可以繼續從隊列中取出數據,分發數據。
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

運行時序如下所示,該圖描述了一個Message數據加入空隊列,到數據被取出消化的流程。
在這裏插入圖片描述

總結:Android消息隊列中的Message入列和出列,都是基於單鏈表來實現,其隊列排序的核心變量就是Message內部的when變量,when變量是一個時間戳,由Handler給該變量賦值,延遲消息,定時消息,都是根據when變量來實現。 在這裏分析隊列的邏輯時,發現了跟jni部分的通信,主要是nativePollOnce,nativeWake方法,這兩個方法實際是實現了空隊列阻塞,以及喚醒的功能,底層使用epoll機制實現。

上述分析中講了Handler的消息入列與出列的具體實現時,其中有碰到MessageQueue中使用了兩個jni的函數,nativePollOnce和nativeWake,nativePollOnce的作用一個是保證了Looper循環在消息隊列中沒有數據或者鏈頭的Message的when變量大於當前時間戳時能夠阻塞,從而減少CPU的資源使用率,nativeWake的作用則是在Looper循環被阻塞的時候,當有新的消息加入到隊列中執行時,能夠及時喚醒阻塞的循環,保證消息能夠及時處理。那這兩個函數是如何實現阻塞/喚醒的呢? 首先跟蹤兩者的本地函數定義在MessageQueue中:

 private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
 private native static void nativeWake(long ptr);

對應的JNI函數在android_os_MessageQueue.cpp中,如下:

static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
        jlong ptr, jint timeoutMillis) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}

static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->wake();
}

jni函數中並沒有做處理,只是調用NativeMessageQueue的方法,繼續跟蹤兩個函數在NativeMessageQueue的執行如下:

void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
    mPollEnv = env;
    mPollObj = pollObj;
    mLooper->pollOnce(timeoutMillis);
    mPollObj = NULL;
    mPollEnv = NULL;

    if (mExceptionObj) {
        env->Throw(mExceptionObj);
        env->DeleteLocalRef(mExceptionObj);
        mExceptionObj = NULL;
    }
}

void NativeMessageQueue::wake() {
    mLooper->wake();
}

發現最終的實現都是在Looper.cpp中,在分析Looper.cpp的源碼時,有必要了解一下epoll的基礎概念,以及使用方式,參考這篇文章
native Looper對象的初始化,在android_os_Message.cpp文件的NativeMessageQueue構造函數中完成

NativeMessageQueue::NativeMessageQueue() :
        mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
    //從當前線程的私有域中查看是否有已經創建的Looper
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
       //如果沒有已經存在的Looper,則創建新的
        mLooper = new Looper(false);
        Looper::setForThread(mLooper);
    }
}

繼續跟蹤Looper的構造函數

Looper::Looper(bool allowNonCallbacks) :
        mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
        mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),
        mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
    //eventfd 是 Linux 的一個系統調用,創建一個文件描述符用於事件通知
     //具體參數介紹以及使用方式,請看[這裏](http://man7.org/linux/man-pages/man2/eventfd2.2.html)
    mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
    LOG_ALWAYS_FATAL_IF(mWakeEventFd < 0, "Could not make wake event fd: %s", strerror(errno));
    AutoMutex _l(mLock);
    rebuildEpollLocked();
}

void Looper::rebuildEpollLocked() {
    // Close old epoll instance if we have one.
    if (mEpollFd >= 0) {
#if DEBUG_CALLBACKS
        ALOGD("%p ~ rebuildEpollLocked - rebuilding epoll set", this);
#endif
        close(mEpollFd);
    }

    // Allocate the new epoll instance and register the wake pipe.
    //mEpollFd是epoll創建的文件描述符
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));

    struct epoll_event eventItem;
    memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
    //標記mWakeEventFd對read操作有效
    eventItem.events = EPOLLIN;
    eventItem.data.fd = mWakeEventFd;
    //epoll_ctl執行EPOLL_CTL_ADD參數的操作的意思是將mWakeEventFd加入到監聽鏈表中,當有read操作時,喚醒mWakeEventFd的wait等待。
    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake event fd to epoll instance: %s",
                        strerror(errno));
    ......
    ......

Looper的構造函數中,通過epoll_create獲得了一個epoll的文件描述符,再通過epoll_ctl將mWakeEventFd添加到epoll中監聽。從上面個跟蹤的流程得知,我們調用nativePollOnce()函數,最終執行的地方是在Looper的pollOnce中,代碼如下:

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    for (;;) {
        ......
        ......
        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);
    }
}

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 %" PRId64 "ns, adjusted timeout: timeoutMillis=%d",
                this, mNextMessageUptime - now, timeoutMillis);
#endif
    }

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

    // We are about to idle.
    mPolling = true;

    //開始等待事件的觸發, timeoutMillis是在該時間內,如果沒有獲取到事件,則自動返回,爲-1則一直等待到有事件過來,爲0則不管有沒有事件,都    
    //立即返回。 Handler在設計的時候,首先會查詢一次隊列,如果沒有數據,則立即返回,然後重新pollOnce走到這裏等待新的數據過來, 往    
    // mWakeEventFd寫數據,纔會喚醒返回。
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

    // No longer idling.
    mPolling = false;

    // Acquire lock.
    mLock.lock();

    // Rebuild epoll set if needed.
    if (mEpollRebuildRequired) {
        mEpollRebuildRequired = false;
        rebuildEpollLocked();
        goto Done;
    }

   //請求出錯
    // Check for poll error.
    if (eventCount < 0) {
        if (errno == EINTR) {
            goto Done;
        }
        ALOGW("Poll failed with an unexpected error: %s", strerror(errno));
        result = POLL_ERROR;
        goto Done;
    }

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

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

在隊列消息爲空的情況下,那麼我們就會阻塞在poll_inner的epoll_wait處,如果有新消息加入隊列,則上層會調用nativeWake,最終對應Looper中的wake函數將epoll_wait喚醒。


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

    uint64_t inc = 1;
    //往mWakeEventFd中寫數據, mWakeEventFd監聽到有事件觸發,則使epoll_wait返回。
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
    if (nWrite != sizeof(uint64_t)) {
        if (errno != EAGAIN) {
            ALOGW("Could not write wake signal: %s", strerror(errno));
        }
    }
}

至此,Looper.cpp中實現隊列的阻塞喚醒的功能已經完成,總結一下:

  1. Looper.cpp對象是在android_os_MessageQueue的nativeInit函數被調用時初始化
  2. Looper.cpp的構造函數中,使用eventfd函數創建了一個mWakeEventFd的文件描述符用於事件通知,並使用epoll_create函數創建了一個epoll的文件描述符。然後調用epoll_ctl將mWakeEventFd加入到了事件監聽鏈中。
  3. 初始化的隊列爲空,請求數據的時候,首先會走到pollOnce中,傳入的timeoutMillis值爲0,則epoll_wait會立即返回,然後MessageQueueh會進行timeoutMills = -1的第二次pollOnce, 這時候就阻塞在epoll_wait函數處。
  4. 當有新數據加入隊列時,調用wake函數往mWakeEventFd中寫數據, 此時觸發epoll_wait阻塞中斷,pollOnce返回,上層MessageQueue中的next()函數也將執行完畢,並將Message丟給Handler處理
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章