Android消息機制解析(native層)

前言

MessageQueue中有多個native方法,MessaeQueue是Android消息機制的Java層和native層的連接紐帶,Android的java層和native層通過JNI調用打通,java層和native各有一套消息機制,實現不一樣,本文講解native層的Android消息機制,瞭解了native層的消息機制,你就能明白爲什麼java層的loop方法是死循環但卻不會消耗性能這個問題。

native層消息機制架構圖

  • MessageQueue — 裏面有一個Looper,和java層的MessageQueue同名;
  • NativeMessageQueue — MessageQueue的繼承類,native層的消息隊列,只是一個代理類,其大部分方法操作都轉交給Looper的方法;
  • Looper — native層的Looper,其功能相當於java層的Handler,它可以取出消息,發送消息,處理消息;
  • MessageHandler — native層的消息處理類,Looper把處理消息邏輯轉交給此類;
  • WeakMessageHanlder — MessageHandler的繼承類,也是消息處理類,但最終還是會把消息處理邏輯轉交給MessageHandler。

java層的MessageQueue

要講解native層的消息機制,我們需要從java層消息機制調用到的MessageQueue的native方法講起,MessageQueue中所有的native方法如下:

//MessageQueue.java
public final class MessageQueue {
    private native static long nativeInit();
    private native static void nativeDestroy(long ptr);
    private native void nativePollOnce(long ptr, int timeoutMillis); 
    private native static void nativeWake(long ptr);
    private native static boolean nativeIsPolling(long ptr);
    private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
    //...
}

我們主要講解三個:nativeInit()、 nativePollOnce(long ptr, int timeoutMillis)和 nativeWake(long ptr),nativeInit方法在java層的MessageQueue構造的時候調用到,nativePollOnce方法在java層的MessageQueue的next方法調用到,nativeWake方法在java層的MessageQueue的enqueueuMessage方法調用到。

1、nativeInit()

java層中,在ActivityThread的main方法創建UI線程的消息循環,Looper.prepareMainLooper -> Looper.prepare -> new Looper -> new MessageQueue,MessageQueue是在Looper的構造中創建的,在MessageQueue的構造中:

//MessageQueue.java
MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}

在java層中,mPtr保存了nativeInit()返回的值,nativeInit方法的實現在android_os_MessageQueue.cpp文件中的android_os_MessageQueue_nativeInit方法中,該方法源碼如下:

//frameworks/base/core/jni/android_os_MessageQueue.cpp
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {   
    //創建native消息隊列NativeMessageQueue
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    //...
    //增加引用計數
    nativeMessageQueue->incStrong(env);
    //使用C++強制類型轉換符reinterpret_cast把NativeMessageQueue指針強轉成long類型並返回到java層
    return reinterpret_cast<jlong>(nativeMessageQueue);
}

可以看到在android_os_MessageQueue_nativeInit方法中會創建一個NativeMessageQueue對象,並增加其引用計數,並將NativeMessageQueue指針mPtr保存在Java層的MessageQueue中,現在我們來看NativeMessageQueue的構造函數, 如下:

//frameworks/base/core/jni/android_os_MessageQueue.cpp
NativeMessageQueue::NativeMessageQueue() 
    : mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
    //獲取TLS中的Looper(Looper::getForThread相當於java層的Looper.mLooper中的ThreadLocal.get方法) 
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
        //創建native層的Looper
        mLooper = new Looper(false);
        //保存Looper到TLS中(Looper::setForThread相當於java層的ThreadLocal.set方法)
        Looper::setForThread(mLooper);
    }
}

在NativeMessageQueue的構造中會先調用Looper的getForThread方法從當前線程獲取Looper對象,如果爲空,就會創建一個Looper並調用Looper的setForThread方法設置給當前線程。

關於TLS更多信息可以查看ThreadLocal原理解析

也就是說Looper和MessageQueue在java層和native層都有,但它們的功能並不是一一對應,此處native層的Looper與Java層的Looper沒有任何的關係,只是在native層重實現了一套類似功能的邏輯,我們來看看native層在創建Looper時做了什麼,Looper的構造函數如下:

//system/core/libutils/Looper.cpp
Looper::Looper(bool allowNonCallbacks) :
        mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
        mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),
        mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
    //1、構造喚醒事件的fd(文件描述符)
    mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
    //...
    //2、重建epoll事件
    rebuildEpollLocked();
}

這裏我們忽略一大堆字段賦值,只關注字段mWakeEventFd和函數:rebuildEpollLocked(),mWakeEventFd就是用於喚醒線程的文件描述符,而rebuildEpollLocked方法就是用來重建epoll事件,建立起epoll機制,通過epoll機制監聽各種文件描述符.

文件描述符是什麼?它就是一個int值,又叫做句柄,在Linux中,打開或新建一個文件,它會返回一個文件描述符,讀寫文件需要使用文件描述符來指定待讀寫的文件,所以文件描述符就是指代被打開的文件,所有對這個文件的IO操作都要通過文件描述符

但其實文件描述符也不僅僅是指代文件,它還有更多的含義,可以看後文的epoll機制解釋。

rebuildEpollLocked方法的核心源碼如下:

//system/core/libutils/Looper.cpp
void Looper::rebuildEpollLocked() {
    //1、關閉舊的管道
    if (mEpollFd >= 0) {
        close(mEpollFd);
    }

    //2、創建一個新的epoll文件描述符,並註冊wake管道
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);//EPOLL_SIZE_HINT爲8

    struct epoll_event eventItem;
    memset(& eventItem, 0, sizeof(epoll_event)); //置空eventItem
    //3、設置監聽事件類型和需要監聽的文件描述符
    eventItem.events = EPOLLIN;//監聽可讀事件(EPOLLIN)
    eventItem.data.fd = mWakeEventFd;//設置喚醒事件的fd(mWakeEventFd)

    //4、將喚醒事件fd(mWakeEventFd)添加到epoll文件描述符(mEpollFd),並監聽喚醒事件fd(mWakeEventFd)
    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);   
    
    //5、將各種事件,如鍵盤、鼠標等事件的fd添加到epoll文件描述符(mEpollFd),進行監聽
    for (size_t i = 0; i < mRequests.size(); i++) {
        const Request& request = mRequests.valueAt(i);
        struct epoll_event eventItem;
        request.initEventItem(&eventItem);

        int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, request.fd, & eventItem);
        if (epollResult < 0) {
            ALOGE("Error adding epoll events for fd %d while rebuilding epoll set: %s",
                  request.fd, strerror(errno));
        }
    }
}
}

Looper的構造函數中涉及到Linux的epoll機制,epoll機制是Linux最高效的I/O複用機制, 使用一個文件描述符管理多個描述符,這裏簡單介紹一下它的使用方法:

epoll操作過程有3個方法,分別是:

1、int epoll_create(int size):用於創建一個epoll的文件描述符,創建的文件描述符可監聽size個文件描述符;
參數介紹
size:size是指監聽的描述符個數

2、int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event): 用於對需要監聽的文件描述符fd執行op操作,比如將fd添加到epoll文件描述符epfd;
參數介紹:
epfd:是epoll_create()的返回值
op:表示op操作,用三個宏來表示,分別爲EPOLL_CTL_ADD(添加)、EPOLL_CTL_DEL(刪除)和EPOLL_CTL_MOD(修改)
fd:需要監聽的文件描述符
epoll_event:需要監聽的事件,有4種類型的事件,分別爲EPOLLIN(文件描述符可讀)、EPOLLOUT(文件描述符可寫), EPOLLERR(文件描述符錯誤)和EPOLLHUP(文件描述符斷)

3、int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout): 等待事件的上報, 該函數返回需要處理的事件數目,如返回0表示已超時;
參數介紹
epfd:等待epfd上的io事件,最多返回maxevents個事件
events:用來從內核得到事件的集合
maxevents:events數量,該maxevents值不能大於創建epoll_create()時的size
timeout:超時時間(毫秒,0會立即返回)

關於更多資料可以自行查找資料,有Linux基礎的可以閱讀源碼解讀epoll內核機制

要了解epoll機制,首先要知道,在Linux中,**文件、socket、管道(pipe)**等可以進行IO操作的對象都可以稱之爲流,既然是IO流,那肯定會有兩端:read端和write端,我們可以創建兩個文件描述符wiretFd和readFd,對應read端和write端,當流中沒有數據時,讀線程就會阻塞(休眠)等待,當寫線程通過wiretFd往流的wiret端寫入數據後,readFd對應的read端就會感應到,喚醒讀線程讀取數據,大概就是這樣的一個讀寫過程,讀線程進入阻塞後,並不會消耗CPU時間,這是epoll機制高效的原因之一。

說了一大堆,我們再回到rebuildEpollLocked方法,rebuildEpollLocked方法中使用了epoll機制,在Linux中,線程之間的通信一般是通過管道(pipe),在rebuildEpollLocked方法中,首先通過epoll_create方法創建一個epoll專用文件描述符(mEpollFd),同時創建了一個管道,然後設置監聽可讀事件類型(EPOLLIN),最後通過epoll_ctl方法把Looper對象中的喚醒事件的文件描述符(mWakeEventFd)添加到epoll文件描述符的監控範圍內,當mWakeEventFd那一端發生了寫入,這時mWakeEventFd可讀,就會被epoll監聽到(epoll_wait方法返回),我們發現epoll文件描述符不僅監聽了mWakeEventFd,它還監聽了其他的如鍵盤、鼠標等事件的文件描述符,所以一個epoll文件描述符可以監聽多個文件描述符

至此,native層的MessageQueue和Looper就構建完畢,底層通過管道與epoll機制也建立了一套消息機制。

我們跟着MessageQueue#nativeInit()一路走下來,這裏小結一下:

  • 1、首先java層的Looper對象會在構造函數中創建java層的MessageQueue對象;
  • 2、 java層的MessageQueue對象又會調用nativeInit函數初始化native層的NativeMessageQueue,NativeMessageQueue的構造函數又會創建native層的Looper,並且在Looper中通過管道與epoll機制建立一套消息機制;
  • 3、native層構建完畢,將NativeMessageQueue對象轉換爲一個long類型存儲到java層的MessageQueue的mPtr中。

2、nativePollOnce()

在native層通過epoll機制也建立了一套消息機制後,java層的消息循環也就創建好,在此之後就會在java層中啓動消息循環,Looper.loop -> MessageQueue.next,在java層中每次循環去讀消息時,都會調用MessageQueue的next函數,如下:

//MessageQueue.java
 Message next() {
 	//...
 	for(;;){
 		 nativePollOnce(ptr, nextPollTimeoutMillis);
 		 //...
 	}
 	//...
 }

next方法返回一個Message,沒有消息時,會調用nativePollOnce方法進入阻塞,nativePollOnce方法的實現在android_os_MessageQueue.cpp文件中的android_os_MessageQueue_nativePollOnce方法中,該方法的源碼如下:

//frameworks/base/core/jni/android_os_MessageQueue.cpp
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
        jlong ptr, jint timeoutMillis) {
    //把ptr強轉爲NativeMessageQueue
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}

ptr是從java層傳過來的mPtr的值,mPtr在初始化時保存了NativeMessageQueue的指針,此時首先把傳遞進來的ptr轉換爲NativeMessageQueue,然後調用NativeMessageQueue的pollOnce函數,該函數核心源碼如下:

//frameworks/base/core/jni/android_os_MessageQueue.cpp
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
    //...
    //核心是調用了native層的Looper的pollOnce方法
    mLooper->pollOnce(timeoutMillis);  
    //...
}

NativeMessageQueue是一個代理類,所以它把邏輯轉交給Looper,這段代碼主要就是調用了native層的Looper的**pollOnce(timeoutMillis)**方法,該方法定義在Looper.h文件中,如下:

//system/core/libutils/Looper.h
inline int pollOnce(int timeoutMillis) {
    //調用了帶4個參數的pollOnce方法
    return pollOnce(timeoutMillis, NULL, NULL, NULL);
}

pollOnce(timeoutMillis)方法會調用Looper的polOnce(timeoutMillis, NULL, NULL, NULL),該方法的實現在Looper.cpp文件中,如下:

//system/core/libutils/Looper.cpp
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    //一個死循環
    for (;;) {
        //...
        //當result不等於0時,就會跳出循環,返回到java層
        if (result != 0) {
            //...
            return result;
        }
        //處理內部輪詢
        result = pollInner(timeoutMillis);
    }
}

該方法內部是一個死循環,核心在於調用了pollInner方法,pollInner方法返回一個int值result,代表着本次輪詢是否成功處理了消息,當result不等於0時,就會跳出循環,返回到java層繼續處理java層消息,result有以下4種取值:

enum {
    //表示Looper的wake方法被調用,即管道的寫端的write事件觸發
    POLL_WAKE = -1,

    //表示某個被監聽fd被觸發。
    POLL_CALLBACK = -2,

    //表示等待超時
    POLL_TIMEOUT = -3,
    
    //表示等待期間發生錯誤
    POLL_ERROR = -4,
};

我們接着來看pollInner方法,如下:

//system/core/libutils/Looper.cpp
int Looper::pollInner(int timeoutMillis) { 
    //timeoutMillis等於-1,並且mNextMessageUptime不等於LLONG_MAX
    //這說明java層沒有消息但是native層有消息處理,這時在epoll_wait中,線程不能因爲timeoutMillis等於-1而進入休眠,它還需要處理native層消息
    //所以這裏會根據mNextMessageUptime把timeoutMillis更新爲大於0的值
    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爲大於0的值,這個大於0的值就是需要等待多久後,纔會到達native層消息的執行時間,等待timeoutMillis後,epoll_wait就會返回處理native層消息
            timeoutMillis = messageTimeoutMillis;
        }
        //...
    }
    int result = POLL_WAKE;
    //...
    //事件集合(eventItems),EPOLL_MAX_EVENTS爲最大事件數量,它的值爲16
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    
    //1、等待事件發生或者超時(timeoutMillis),如果有事件發生,就從管道中讀取事件放入事件集合(eventItems)返回,如果沒有事件發生,進入休眠等待,如果timeoutMillis時間後還沒有被喚醒,就會返回
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);  
    
    //獲取鎖
    mLock.lock();
    
    //...省略的邏輯是:如果eventCount <= 0 都會直接跳轉到Done:;標記的代碼段

    //2、遍歷事件集合(eventItems),檢測哪一個文件描述符發生了IO事件
    for (int i = 0; i < eventCount; i++) {
        //取出文件描述符
        int fd = eventItems[i].data.fd;
        //取出事件類型
        uint32_t epollEvents = eventItems[i].events;
        if (fd == mWakeEventFd) {//如果文件描述符爲mWakeEventFd
            if (epollEvents & EPOLLIN) {//並且事件類型爲EPOLLIN(可讀事件)
                //這說明當前線程關聯的管道的另外一端寫入了新數據
                //調用awoken方法不斷的讀取管道數據,直到清空管道
                awoken();
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents);
            }
        } else {//如果是其他文件描述符,就進行它們自己的處理邏輯
           //...
        }
    }
    
    //2、下面是處理Native的Message
    Done:;
    //mNextMessageUptime如果沒有值,會被賦值成LLONG_MAX,但是如果mNextMessageUptime已經有值,它還是保持原來的值
    mNextMessageUptime = LLONG_MAX;
    //mMessageEnvelopes是一個Vector集合,它代表着native中的消息隊列
    while (mMessageEnvelopes.size() != 0) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        //取出MessageEnvelope,MessageEnvelop有收件人Hanlder和消息內容Message
        const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);
        //判斷消息的執行時間
        if (messageEnvelope.uptime <= now) {//消息到達執行時間
            {
        	    //獲取native層的Handler
                sp<MessageHandler> handler = messageEnvelope.handler;
                //獲取native層的消息
                Message message = messageEnvelope.message;
                mMessageEnvelopes.removeAt(0);
                mSendingMessage = true;
                //釋放鎖
                mLock.unlock();
                //通過MessageHandler的handleMessage方法處理native層的消息
                handler->handleMessage(message);
            }
            mLock.lock();
            mSendingMessage = false;
            //result等於POLL_CALLBACK,表示某個監聽事件被觸發
            result = POLL_CALLBACK;
        } else {//消息還沒到執行時間
            //把消息的執行時間賦值給mNextMessageUptime
            mNextMessageUptime = messageEnvelope.uptime;
            //跳出循環,進入下一次輪詢
            break;
        }
    }
    //釋放鎖
    mLock.unlock();
    //...
    return result;
}

pollInner方法很長,省略了一大堆代碼,這裏講解一些核心的點,pollInner實際上就是從管道中讀取事件,並且處理這些事件,pollInner方法可分爲3部分:

1、執行epoll_wait方法,等待事件發生或者超時

這裏再次貼出epoll_wait方法的作用:

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout): 等待事件的上報, 該函數返回需要處理的事件數目,如返回0表示已超時;
參數介紹
epfd:等待epfd上的io事件,最多返回maxevents個事件;
events:用來從內核得到事件的集合;
maxevents:events數量,該maxevents值不能大於創建epoll_create()時的size;
timeout:超時時間(毫秒,0會立即返回).

epoll_wait方法就是用來等待事件發生返回或者超時返回,它是一個阻塞方法, 如果epoll_create方法創建的epoll文件描述符(mEpollFd)所監聽的任何事件發生,epoll_wait方法就會監聽到,並把發生的事件從管道讀取放入事件集合(eventItems)中,返回發生的事件數目eventCount,如果沒有事件,epoll_wait方法就會讓當前線程進入休眠,如果休眠timeout後還沒有其他線程寫入事件喚醒,就會返回,而此時返回的eventCount == 0,表示已經超時,timeout就是從java層一直傳過來的nextPollTimeoutMillis,它的含義和nextPollTimeoutMillis一樣,當timeout == -1時,表示native層的消息隊列中沒有消息,會一直等待下去,直到被喚醒,當timeout = 0時或到達timeout 時,它會立即返回。

我們發現epoll機制只會把發生了的事件放入事件集合中,這樣線程對事件集合的每一個事件的相應IO操作都有意義,這也是epoll機制高效的原因之一。

2、遍歷事件集合(eventItems),檢測哪一個文件描述符發生了IO事件

遍歷事件集合中,如果是mWakeEventFd,就調用awoken方法不斷的讀取管道數據,直到清空管道,如果是其他的文件描述符發生了IO事件,讓它們自己處理相應邏輯。

3、處理native層的Message

只要epoll_wait方法返回後,都會進入Done標記位的代碼段, 就開始處理處理native層的Message, 在此之前先講解一下MessageEnvelope,正如其名字,信封,其結構體定義在Looper.h中,如下:

//system/core/libutils/Looper.h
class Looper : public RefBase { 
    struct MessageEnvelope {
        MessageEnvelope() : uptime(0) { }

        MessageEnvelope(nsecs_t u, const sp<MessageHandler> h, const Message& m) : uptime(u), handler(h), message(m) {}

        nsecs_t uptime;
        //收信人handler
        sp<MessageHandler> handler;
        //信息內容message
        Message message;
    };   
    //...
}

MessageEnvelope裏面記錄着收信人(handler,MessageHandler類型,是一個消息處理類),發信時間(uptime),信件內容(message,Message類型),Message結構體,消息處理類MessageHandler都定義在Looper.h文件中, 在java層中,消息隊列是一個鏈表,在native層中,消息隊列是一個C++的Vector向量,Vector存放的是MessageEnvelope元素,接下來就進入一個while循環,裏面會判斷消息是否達到執行時間,如果到達執行時間,就會取出信封中的MessageHandlerMessage,把Message交給MessageHandler的handlerMessage方法處理;如果沒有到達執行時間,就會更新mNextMessageUptime爲消息的執行時間,這樣在下一次輪詢時,如果由於java層沒有消息導致timeoutMillis等於-1,就會根據mNextMessageUptime更新timeoutMillis爲需要等待執行的時間,超時後返回繼續處理native層消息隊列的頭部信息。

我們跟着MessageQueue#nativePollOnce()一路走下來,小結一下:

  • 1、當在java層通過Looper啓動消息循環後,就會走到MessageQueue的nativePollOnce方法,在該方法native實現中,會把保存在java層的mPtr再轉換爲NativeMessageQueue;
  • 2、然後調用NativeMessageQueue的pollOnce方法,該方法中最終會調用native層的Looper的pollInner方法,Looper的pollInner方法是阻塞方法,等從管道取到事件或超時就會返回,並通過native層的Handler處理native層的Message消息;
  • 3、處理完native層消息後,又會返回到java層處理java層的消息,這倆個層次的消息都通過java層的Looper消息循環進行不斷的獲取,處理等操作.

可以看到,native層的NativeMessageQueue實際上並沒有做什麼實際工作,只是把操作轉發給native層的Looper,而native層的Looper則扮演了java層的Handle角色,它可以取出,發送,處理消息,

3、nativeWake()

我們在Java層通過Hanlder發送消息時,實際是把消息添加到消息隊列,Handler.sendXX -> Handler.enqueueMessage -> MessageQueuue.enqueueMessage,最終會調用到MessageQueue的enqueueMessage方法,,如下:

//MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
    //...
    synchronized (this) {
        //...
        if (needWake) {
            nativeWake(mPtr);
        }
    }
}

該方法中如果需要進行喚醒MessageQueuue的話,都會調用到nativeWake方法,,MessageQueue的nativeWake方法的實現在android_os_MessageQueue.cpp文件中的android_os_MessageQueue_nativeWake方法中,該方法的源碼如下:

//frameworks/base/core/jni/android_os_MessageQueue.cpp
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->wake();
}

首先把傳遞進來的ptr轉換爲NativeMessageQueue,然後調用NativeMessageQueue的wake函數,該函數源碼如下:

//frameworks/base/core/jni/android_os_MessageQueue.cpp
void NativeMessageQueue::wake() {
    mLooper->wake();
}

前面說過在native層中NativeMessageQueue只是一個代理Looper的角色,該方法把操作轉發給native層的Looper,Looper的wake方法核心源碼如下:

//system/core/libutils/Looper.cpp
void Looper::wake() {
    uint64_t inc = 1;
    //使用write函數通過mWakeEventFd往管道寫入字符inc
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
    //...
}

Looper的wake方法其實是使用write函數通過mWakeEventFd往管道寫入字符inc,其中TEMP_FAILURE_RETRY 是一個宏定義, 當執行write方法失敗後,會不斷重複執行,直到執行成功爲止,在nativeinit中,我們已經通過epoll_create方法監聽了mWakeEventFd的可讀事件,當mWakeEventFd可讀時,epoll文件描述符就會監聽到,這時epoll_wait方法就會從管道中讀取事件返回,返回後就執行消息處理邏輯,所以這裏的往管道寫入字符inc,其實起到一個通知的作用,告訴監聽的線程有消息插入了消息隊列了,快點醒過來(因爲進入了休眠狀態)處理一下。

我們跟着MessageQueue#nativeWake一路走下來,小結一下:

  • 1、在java層插入消息到消息隊列後,就會根據需要判斷是否要調用nativeWake方法,如果調用,就轉到2。
  • 2、在nativeWake方法native實現中,會把保存在java層的mPtr再轉換爲NativeMessageQueue,然後調用NativeMessageQueue的wake方法,最終調用Looper的wake方法。
  • 3、前面講到Looper::pollInner方法是一個阻塞操作,當管道中沒有事件時當前線程就會進入休眠等待,當管道有事件就會立即返回,從管道中讀取事件並處理,而Looper::wake方法就是一個喚醒操作,它就是通過前面創建的喚醒事件文件描述符mWakeEventFd來往管道中寫入內容,這時另外等待管道事件的線程就會被喚醒處理事件。

4、小結

1、在創建java層的MessageQueue對象同時會在構造中調用nativeInit方法創建native層的NativeMessageQueue,在創建NativeMessageQueue同時會在構造中創建native層的Looper對象,並把它保存到TLS區域中,然後返回NativeMessageQueue的指針給java層的mPtr保存;

2、在創建Looper時會在構造中通過管道與epoll機制建立一套native層的消息機制,它首先創建一個喚醒文件描述符mWakeEventFd,然後使用epoll_create方法創建一個epoll文件描述符mEpollFd和管道,然後使用epoll_ctl把mWakeEventFd添加到mEpollFd的監控範圍內;

3、當java層使用Handler發送消息時,會把消息插入到消息隊列中,然後根據情況調用nativeWake方法喚醒阻塞線程,nativeWake方法會調用到native層的Looper的wake方法,裏面會通過mWakeEventFd往管道中寫入一個字符,喚醒阻塞線程處理消息;

4、當java層使用Looper的loop方法取消息時,如果沒有消息,調用nativePollOnce方法進入阻塞狀態,這時nativePollOnce方法會調用到native層的Looper的pollInner方法,裏面會使用epoll_wait等待事件發生或超時,當mEpollFd監聽的**任何文件描述符(包括mWakeEventFd)**的相應IO事件發生時,epoll_wait方法就會返回,返回就會通過native層的MessageHandler處理native層的Message,處理完native層消息後,再返回處理java層的消息。

總結

Java層和Native層的MessageQueue通過JNI建立關聯,從而使得MessageQueue成爲Java層和Native層的樞紐,既能處理上層消息,也能處理native層消息,而Handler/Looper/Message這三大類在Java層與Native層之間沒有任何的關聯,只是分別在Java層和Native層的消息模型中具有相似的功能,都是彼此獨立的,各自實現相應的邏輯。

這裏我們可以回答爲什麼java層的loop方法是死循環但卻不會消耗性能這個問題:

因爲java層的消息機制是依賴native層的消息機制來實現的,而native層的消息機制是通過Linux的管道和epoll機制實現的,epoll機制是一種高效的IO多路複用機制, 它使用一個文件描述符管理多個描述符,java層通過mPtr指針也就共享了native層的epoll機制的高效性,當loop方法中取不到消息時,便阻塞在MessageQueue的next方法,而next方法阻塞在nativePollOnce方法,nativePollOnce方法通過JNI調用進入到native層中去,最終nativePollOnce方法阻塞在epoll_wait方法中,epoll_wait方法會讓當前線程釋放CPU資源進入休眠狀態,等到下一個消息到達(mWakeEventFd會往管道寫入字符)或監聽的其他事件發生時就會喚醒線程,然後處理消息,所以就算loop方法是死循環,當線程空閒時,它會進入休眠狀態,不會消耗大量的CPU資源

以上就是本文的所有內容,希望大家有所收穫。

參考資料:

epoll、looper.loop主線程阻塞

Android消息機制2-Handler

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章