Android消息機(jī)制(native層)

前言

MessageQueue中有多個(gè)native方法爽丹,MessaeQueue是Android消息機(jī)制的Java層和native層的連接紐帶,Android的java層和native層通過(guò)JNI調(diào)用打通到涂,java層和native各有一套消息機(jī)制皆警,實(shí)現(xiàn)不一樣珊皿,本文講解native層的Android消息機(jī)制,了解了native層的消息機(jī)制懊缺,你就能明白為什么java層的loop方法是死循環(huán)但卻不會(huì)消耗性能這個(gè)問(wèn)題疫稿。

native層消息機(jī)制架構(gòu)圖

  • MessageQueue --- 里面有一個(gè)Looper,和java層的MessageQueue同名;
  • NativeMessageQueue --- MessageQueue的繼承類鹃两,native層的消息隊(duì)列遗座,只是一個(gè)代理類,其大部分方法操作都轉(zhuǎn)交給Looper的方法;
  • Looper --- native層的Looper俊扳,其功能相當(dāng)于java層的Handler途蒋,它可以取出消息,發(fā)送消息馋记,處理消息号坡;
  • MessageHandler --- native層的消息處理類,Looper把處理消息邏輯轉(zhuǎn)交給此類;
  • WeakMessageHanlder --- MessageHandler的繼承類梯醒,也是消息處理類宽堆,但最終還是會(huì)把消息處理邏輯轉(zhuǎn)交給MessageHandler。

java層的MessageQueue

要講解native層的消息機(jī)制茸习,我們需要從java層消息機(jī)制調(diào)用到的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);
    //...
}

我們主要講解三個(gè):nativeInit()、 nativePollOnce(long ptr, int timeoutMillis)和 nativeWake(long ptr),nativeInit方法在java層的MessageQueue構(gòu)造的時(shí)候調(diào)用到籽慢,nativePollOnce方法在java層的MessageQueue的next方法調(diào)用到浸遗,nativeWake方法在java層的MessageQueue的enqueueuMessage方法調(diào)用到。

1箱亿、nativeInit()

java層中跛锌,在ActivityThread的main方法創(chuàng)建UI線程的消息循環(huán),Looper.prepareMainLooper -> Looper.prepare -> new Looper -> new MessageQueue届惋,MessageQueue是在Looper的構(gòu)造中創(chuàng)建的察净,在MessageQueue的構(gòu)造中:

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

在java層中,mPtr保存了nativeInit()返回的值盼樟,nativeInit方法的實(shí)現(xiàn)在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) {   
    //創(chuàng)建native消息隊(duì)列NativeMessageQueue
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    //...
    //增加引用計(jì)數(shù)
    nativeMessageQueue->incStrong(env);
    //使用C++強(qiáng)制類型轉(zhuǎn)換符reinterpret_cast把NativeMessageQueue指針強(qiáng)轉(zhuǎn)成long類型并返回到j(luò)ava層
    return reinterpret_cast<jlong>(nativeMessageQueue);
}

可以看到在android_os_MessageQueue_nativeInit方法中會(huì)創(chuàng)建一個(gè)NativeMessageQueue對(duì)象锈至,并增加其引用計(jì)數(shù)晨缴,并將NativeMessageQueue指針mPtr保存在Java層的MessageQueue中,現(xiàn)在我們來(lái)看NativeMessageQueue的構(gòu)造函數(shù), 如下:

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

在NativeMessageQueue的構(gòu)造中會(huì)先調(diào)用Looper的getForThread方法從當(dāng)前線程獲取Looper對(duì)象峡捡,如果為空击碗,就會(huì)創(chuàng)建一個(gè)Looper并調(diào)用Looper的setForThread方法設(shè)置給當(dāng)前線程。

關(guān)于TLS更多信息可以查看ThreadLocal原理解析

也就是說(shuō)Looper和MessageQueue在java層和native層都有们拙,但它們的功能并不是一一對(duì)應(yīng)稍途,此處native層的Looper與Java層的Looper沒(méi)有任何的關(guān)系,只是在native層重實(shí)現(xiàn)了一套類似功能的邏輯砚婆,我們來(lái)看看native層在創(chuàng)建Looper時(shí)做了什么械拍,Looper的構(gòu)造函數(shù)如下:

//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、構(gòu)造喚醒事件的fd(文件描述符)
    mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
    //...
    //2装盯、重建epoll事件
    rebuildEpollLocked();
}

這里我們忽略一大堆字段賦值坷虑,只關(guān)注字段mWakeEventFd和函數(shù):rebuildEpollLocked(),mWakeEventFd就是用于喚醒線程的文件描述符埂奈,而rebuildEpollLocked方法就是用來(lái)重建epoll事件迄损,建立起epoll機(jī)制,通過(guò)epoll機(jī)制監(jiān)聽(tīng)各種文件描述符.

文件描述符是什么账磺?它就是一個(gè)int值芹敌,又叫做句柄,在Linux中垮抗,打開(kāi)或新建一個(gè)文件氏捞,它會(huì)返回一個(gè)文件描述符,讀寫文件需要使用文件描述符來(lái)指定待讀寫的文件借宵,所以文件描述符就是指代被打開(kāi)的文件幌衣,所有對(duì)這個(gè)文件的IO操作都要通過(guò)文件描述符

但其實(shí)文件描述符也不僅僅是指代文件,它還有更多的含義,可以看后文的epoll機(jī)制解釋豁护。

rebuildEpollLocked方法的核心源碼如下:

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

    //2、創(chuàng)建一個(gè)新的epoll文件描述符楚里,并注冊(cè)wake管道
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);//EPOLL_SIZE_HINT為8

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

    //4、將喚醒事件fd(mWakeEventFd)添加到epoll文件描述符(mEpollFd)班缎,并監(jiān)聽(tīng)喚醒事件fd(mWakeEventFd)
    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);   
    
    //5蝴光、將各種事件,如鍵盤、鼠標(biāo)等事件的fd添加到epoll文件描述符(mEpollFd),進(jìn)行監(jiān)聽(tīng)
    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的構(gòu)造函數(shù)中涉及到Linux的epoll機(jī)制惧磺,epoll機(jī)制是Linux最高效的I/O復(fù)用機(jī)制, 使用一個(gè)文件描述符管理多個(gè)描述符盔性,這里簡(jiǎn)單介紹一下它的使用方法:

epoll操作過(guò)程有3個(gè)方法,分別是:

1、int epoll_create(int size):用于創(chuàng)建一個(gè)epoll的文件描述符,創(chuàng)建的文件描述符可監(jiān)聽(tīng)size個(gè)文件描述符;
參數(shù)介紹
size:size是指監(jiān)聽(tīng)的描述符個(gè)數(shù)

2、int epoll_ctl(int epfd, int op, int fd, struct epoll_event * event): 用于對(duì)需要監(jiān)聽(tīng)的文件描述符fd執(zhí)行op操作径簿,比如將fd添加到epoll文件描述符epfd;
參數(shù)介紹:
epfd:是epoll_create()的返回值
op:表示op操作,用三個(gè)宏來(lái)表示嘀韧,分別為EPOLL_CTL_ADD(添加)篇亭、EPOLL_CTL_DEL(刪除)和EPOLL_CTL_MOD(修改)
fd:需要監(jiān)聽(tīng)的文件描述符
epoll_event:需要監(jiān)聽(tīng)的事件,有4種類型的事件锄贷,分別為EPOLLIN(文件描述符可讀)译蒂、EPOLLOUT(文件描述符可寫), EPOLLERR(文件描述符錯(cuò)誤)和EPOLLHUP(文件描述符斷)

3、int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout): 等待事件的上報(bào), 該函數(shù)返回需要處理的事件數(shù)目肃叶,如返回0表示已超時(shí);
參數(shù)介紹
epfd:等待epfd上的io事件蹂随,最多返回maxevents個(gè)事件
events:用來(lái)從內(nèi)核得到事件的集合
maxevents:events數(shù)量,該maxevents值不能大于創(chuàng)建epoll_create()時(shí)的size
timeout:超時(shí)時(shí)間(毫秒因惭,0會(huì)立即返回)

關(guān)于更多資料可以自行查找資料岳锁,有Linux基礎(chǔ)的可以閱讀源碼解讀epoll內(nèi)核機(jī)制

要了解epoll機(jī)制,首先要知道蹦魔,在Linux中激率,文件、socket勿决、管道(pipe)等可以進(jìn)行IO操作的對(duì)象都可以稱之為流乒躺,既然是IO流,那肯定會(huì)有兩端:read端和write端低缩,我們可以創(chuàng)建兩個(gè)文件描述符wiretFd和readFd嘉冒,對(duì)應(yīng)read端和write端曹货,當(dāng)流中沒(méi)有數(shù)據(jù)時(shí),讀線程就會(huì)阻塞(休眠)等待讳推,當(dāng)寫線程通過(guò)wiretFd往流的wiret端寫入數(shù)據(jù)后顶籽,readFd對(duì)應(yīng)的read端就會(huì)感應(yīng)到,喚醒讀線程讀取數(shù)據(jù)银觅,大概就是這樣的一個(gè)讀寫過(guò)程礼饱,讀線程進(jìn)入阻塞后,并不會(huì)消耗CPU時(shí)間究驴,這是epoll機(jī)制高效的原因之一镊绪。

說(shuō)了一大堆,我們?cè)倩氐絩ebuildEpollLocked方法洒忧,rebuildEpollLocked方法中使用了epoll機(jī)制蝴韭,在Linux中,線程之間的通信一般是通過(guò)管道(pipe)熙侍,在rebuildEpollLocked方法中万皿,首先通過(guò)epoll_create方法創(chuàng)建一個(gè)epoll專用文件描述符(mEpollFd),同時(shí)創(chuàng)建了一個(gè)管道核行,然后設(shè)置監(jiān)聽(tīng)可讀事件類型(EPOLLIN),最后通過(guò)epoll_ctl方法把Looper對(duì)象中的喚醒事件的文件描述符(mWakeEventFd)添加到epoll文件描述符的監(jiān)控范圍內(nèi)蹬耘,當(dāng)mWakeEventFd那一端發(fā)生了寫入芝雪,這時(shí)mWakeEventFd可讀,就會(huì)被epoll監(jiān)聽(tīng)到(epoll_wait方法返回)综苔,我們發(fā)現(xiàn)epoll文件描述符不僅監(jiān)聽(tīng)了mWakeEventFd惩系,它還監(jiān)聽(tīng)了其他的如鍵盤、鼠標(biāo)等事件的文件描述符如筛,所以一個(gè)epoll文件描述符可以監(jiān)聽(tīng)多個(gè)文件描述符堡牡。

至此,native層的MessageQueue和Looper就構(gòu)建完畢杨刨,底層通過(guò)管道與epoll機(jī)制也建立了一套消息機(jī)制晤柄。

我們跟著MessageQueue#nativeInit()一路走下來(lái),這里小結(jié)一下:

  • 1妖胀、首先java層的Looper對(duì)象會(huì)在構(gòu)造函數(shù)中創(chuàng)建java層的MessageQueue對(duì)象;
  • 2芥颈、 java層的MessageQueue對(duì)象又會(huì)調(diào)用nativeInit函數(shù)初始化native層的NativeMessageQueue,NativeMessageQueue的構(gòu)造函數(shù)又會(huì)創(chuàng)建native層的Looper赚抡,并且在Looper中通過(guò)管道與epoll機(jī)制建立一套消息機(jī)制;
  • 3爬坑、native層構(gòu)建完畢,將NativeMessageQueue對(duì)象轉(zhuǎn)換為一個(gè)long類型存儲(chǔ)到j(luò)ava層的MessageQueue的mPtr中涂臣。

2盾计、nativePollOnce()

在native層通過(guò)epoll機(jī)制也建立了一套消息機(jī)制后,java層的消息循環(huán)也就創(chuàng)建好,在此之后就會(huì)在java層中啟動(dòng)消息循環(huán)署辉,Looper.loop -> MessageQueue.next族铆,在java層中每次循環(huán)去讀消息時(shí),都會(huì)調(diào)用MessageQueue的next函數(shù)涨薪,如下:

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

next方法返回一個(gè)Message骑素,沒(méi)有消息時(shí),會(huì)調(diào)用nativePollOnce方法進(jìn)入阻塞刚夺,nativePollOnce方法的實(shí)現(xiàn)在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強(qiáng)轉(zhuǎn)為NativeMessageQueue
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}

ptr是從java層傳過(guò)來(lái)的mPtr的值,mPtr在初始化時(shí)保存了NativeMessageQueue的指針侠姑,此時(shí)首先把傳遞進(jìn)來(lái)的ptr轉(zhuǎn)換為NativeMessageQueue创橄,然后調(diào)用NativeMessageQueue的pollOnce函數(shù),該函數(shù)核心源碼如下:

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

NativeMessageQueue是一個(gè)代理類莽红,所以它把邏輯轉(zhuǎn)交給Looper妥畏,這段代碼主要就是調(diào)用了native層的Looper的pollOnce(timeoutMillis)方法,該方法定義在Looper.h文件中安吁,如下:

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

pollOnce(timeoutMillis)方法會(huì)調(diào)用Looper的polOnce(timeoutMillis, NULL, NULL, NULL)醉蚁,該方法的實(shí)現(xiàn)在Looper.cpp文件中,如下:

//system/core/libutils/Looper.cpp
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    //一個(gè)死循環(huán)
    for (;;) {
        //...
        //當(dāng)result不等于0時(shí)鬼店,就會(huì)跳出循環(huán)网棍,返回到j(luò)ava層
        if (result != 0) {
            //...
            return result;
        }
        //處理內(nèi)部輪詢
        result = pollInner(timeoutMillis);
    }
}

該方法內(nèi)部是一個(gè)死循環(huán),核心在于調(diào)用了pollInner方法妇智,pollInner方法返回一個(gè)int值result滥玷,代表著本次輪詢是否成功處理了消息,當(dāng)result不等于0時(shí)巍棱,就會(huì)跳出循環(huán)惑畴,返回到j(luò)ava層繼續(xù)處理java層消息,result有以下4種取值:

enum {
    //表示Looper的wake方法被調(diào)用航徙,即管道的寫端的write事件觸發(fā)
    POLL_WAKE = -1,

    //表示某個(gè)被監(jiān)聽(tīng)fd被觸發(fā)如贷。
    POLL_CALLBACK = -2,

    //表示等待超時(shí)
    POLL_TIMEOUT = -3,
    
    //表示等待期間發(fā)生錯(cuò)誤
    POLL_ERROR = -4,
};

我們接著來(lái)看pollInner方法,如下:

//system/core/libutils/Looper.cpp
int Looper::pollInner(int timeoutMillis) {    
    //...
    //事件集合(eventItems)到踏,EPOLL_MAX_EVENTS為最大事件數(shù)量倒得,它的值為16
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    
    //1、等待事件發(fā)生或者超時(shí)(timeoutMillis)夭禽,如果有事件發(fā)生霞掺,就從管道中讀取事件放入事件集合(eventItems)返回,如果沒(méi)有事件發(fā)生讹躯,進(jìn)入休眠等待菩彬,如果timeoutMillis時(shí)間后還沒(méi)有被喚醒缠劝,就會(huì)返回
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);  
    
    //獲取鎖
    mLock.lock();
    
    //...省略的邏輯是:如果eventCount <= 0 都會(huì)直接跳轉(zhuǎn)到Done:;標(biāo)記的代碼段

    //2、遍歷事件集合(eventItems)骗灶,檢測(cè)哪一個(gè)文件描述符發(fā)生了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(可讀事件)
                //這說(shuō)明當(dāng)前線程關(guān)聯(lián)的管道的另外一端寫入了新數(shù)據(jù)
                //調(diào)用awoken方法不斷的讀取管道數(shù)據(jù)惨恭,直到清空管道
                awoken();
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents);
            }
        } else {//如果是其他文件描述符,就進(jìn)行它們自己的處理邏輯
           //...
        }
    }
    
    //2耙旦、下面是處理Native的Message
    Done:;
    mNextMessageUptime = LLONG_MAX;
    //mMessageEnvelopes是一個(gè)Vector集合脱羡,它代表著native中的消息隊(duì)列
    while (mMessageEnvelopes.size() != 0) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        //取出MessageEnvelope,MessageEnvelop有收件人Hanlder和消息內(nèi)容Message
        const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);
        //判斷消息的執(zhí)行時(shí)間
        if (messageEnvelope.uptime <= now) {//消息到達(dá)執(zhí)行時(shí)間
            {
                //獲取native層的Handler
                sp<MessageHandler> handler = messageEnvelope.handler;
                //獲取native層的消息
                Message message = messageEnvelope.message;
                mMessageEnvelopes.removeAt(0);
                mSendingMessage = true;
                //釋放鎖
                mLock.unlock();
                //通過(guò)MessageHandler的handleMessage方法處理native層的消息
                handler->handleMessage(message);
            }
            mLock.lock();
            mSendingMessage = false;
            //result等于POLL_CALLBACK免都,表示某個(gè)監(jiān)聽(tīng)事件被觸發(fā)
            result = POLL_CALLBACK;
        } else {//消息還沒(méi)到執(zhí)行時(shí)間
            mNextMessageUptime = messageEnvelope.uptime;
            //跳出循環(huán)锉罐,進(jìn)入下一次輪詢
            break;
        }
    }
    //釋放鎖
    mLock.unlock();
    //...
}

pollInner方法很長(zhǎng),省略了一大堆代碼绕娘,這里講解一些核心的點(diǎn)脓规,pollInner實(shí)際上就是從管道中讀取事件,并且處理這些事件险领,pollInner方法可分為3部分:

1侨舆、執(zhí)行epoll_wait方法,等待事件發(fā)生或者超時(shí)

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

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout): 等待事件的上報(bào), 該函數(shù)返回需要處理的事件數(shù)目绢陌,如返回0表示已超時(shí);
參數(shù)介紹
epfd:等待epfd上的io事件挨下,最多返回maxevents個(gè)事件;
events:用來(lái)從內(nèi)核得到事件的集合脐湾;
maxevents:events數(shù)量复颈,該maxevents值不能大于創(chuàng)建epoll_create()時(shí)的size;
timeout:超時(shí)時(shí)間(毫秒沥割,0會(huì)立即返回).

epoll_wait方法就是用來(lái)等待事件發(fā)生返回或者超時(shí)返回,它是一個(gè)阻塞方法凿菩, 如果epoll_create方法創(chuàng)建的epoll文件描述符(mEpollFd)所監(jiān)聽(tīng)的任何事件發(fā)生机杜,epoll_wait方法就會(huì)監(jiān)聽(tīng)到,并把發(fā)生的事件從管道讀取放入事件集合(eventItems)中衅谷,返回發(fā)生的事件數(shù)目eventCount椒拗,如果沒(méi)有事件,epoll_wait方法就會(huì)讓當(dāng)前線程進(jìn)入休眠获黔,如果休眠timeout后還沒(méi)有其他線程寫入事件喚醒蚀苛,就會(huì)返回,而此時(shí)返回的eventCount == 0玷氏,表示已經(jīng)超時(shí)堵未,timeout就是從java層一直傳過(guò)來(lái)的nextPollTimeoutMillis,它的含義和nextPollTimeoutMillis一樣盏触,當(dāng)timeout == -1時(shí)渗蟹,表示java層的MessageQueue中沒(méi)有消息块饺,會(huì)一直等待下去,直到被喚醒雌芽,當(dāng)timeout = 0時(shí)或到達(dá)timeout 時(shí)授艰,它會(huì)立即返回;

我們發(fā)現(xiàn)epoll機(jī)制只會(huì)把發(fā)生了的事件放入事件集合中世落,這樣線程對(duì)事件集合的每一個(gè)事件的相應(yīng)IO操作都有意義淮腾,這也是epoll機(jī)制高效的原因之一。

2屉佳、遍歷事件集合(eventItems)谷朝,檢測(cè)哪一個(gè)文件描述符發(fā)生了IO事件

遍歷事件集合中,如果是mWakeEventFd忘古,就調(diào)用awoken方法不斷的讀取管道數(shù)據(jù)徘禁,直到清空管道,如果是其他的文件描述符發(fā)生了IO事件髓堪,讓它們自己處理相應(yīng)邏輯送朱。

3、處理native層的Message

只要epoll_wait方法返回后干旁,都會(huì)進(jìn)入Done標(biāo)記位的代碼段, 就開(kāi)始處理處理native層的Message, 在此之前先講解一下MessageEnvelope驶沼,正如其名字,信封争群,其結(jié)構(gòu)體定義在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;
        //信息內(nèi)容message
        Message message;
    };   
    //...
}

MessageEnvelope里面記錄著收信人(handler,MessageHandler類型换薄,是一個(gè)消息處理類)玉雾,發(fā)信時(shí)間(uptime),信件內(nèi)容(message轻要,Message類型)复旬,Message結(jié)構(gòu)體,消息處理類MessageHandler都定義在Looper.h文件中, 在java層中冲泥,消息隊(duì)列是一個(gè)鏈表驹碍,在native層中,消息隊(duì)列是一個(gè)C++的Vector向量凡恍,Vector存放的是MessageEnvelope元素志秃,接下來(lái)就進(jìn)入一個(gè)while循環(huán),里面會(huì)判斷消息是否達(dá)到執(zhí)行時(shí)間嚼酝,如果到達(dá)執(zhí)行時(shí)間浮还,就會(huì)取出信封中的MessageHandlerMessage,把Message交給MessageHandler的handlerMessage方法處理闽巩。

我們跟著MessageQueue#nativePollOnce()一路走下來(lái)碑定,小結(jié)一下:

  • 1流码、當(dāng)在java層通過(guò)Looper啟動(dòng)消息循環(huán)后,就會(huì)走到MessageQueue的nativePollOnce方法延刘,在該方法native實(shí)現(xiàn)中漫试,會(huì)把保存在java層的mPtr再轉(zhuǎn)換為NativeMessageQueue;
  • 2碘赖、然后調(diào)用NativeMessageQueue的pollOnce方法驾荣,該方法中最終會(huì)調(diào)用native層的Looper的pollInner方法,Looper的pollInner方法是阻塞方法普泡,等從管道取到事件或超時(shí)就會(huì)返回播掷,并通過(guò)native層的Handler處理native層的Message消息;
  • 3撼班、處理完native層消息后歧匈,又會(huì)返回到j(luò)ava層處理java層的消息,這倆個(gè)層次的消息都通過(guò)java層的Looper消息循環(huán)進(jìn)行不斷的獲取砰嘁,處理等操作.

可以看到件炉,native層的NativeMessageQueue實(shí)際上并沒(méi)有做什么實(shí)際工作,只是把操作轉(zhuǎn)發(fā)給native層的Looper矮湘,而native層的Looper則扮演了java層的Handle角色斟冕,它可以取出,發(fā)送缅阳,處理消息磕蛇,

3、nativeWake()

我們?cè)贘ava層通過(guò)Hanlder發(fā)送消息時(shí)十办,實(shí)際是把消息添加到消息隊(duì)列秀撇,Handler.sendXX -> Handler.enqueueMessage -> MessageQueuue.enqueueMessage,最終會(huì)調(diào)用到MessageQueue的enqueueMessage方法,向族,如下:

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

該方法中如果需要進(jìn)行喚醒MessageQueuue的話呵燕,都會(huì)調(diào)用到nativeWake方法,,MessageQueue的nativeWake方法的實(shí)現(xiàn)在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();
}

首先把傳遞進(jìn)來(lái)的ptr轉(zhuǎn)換為NativeMessageQueue,然后調(diào)用NativeMessageQueue的wake函數(shù)弄唧,該函數(shù)源碼如下:

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

前面說(shuō)過(guò)在native層中NativeMessageQueue只是一個(gè)代理Looper的角色适肠,該方法把操作轉(zhuǎn)發(fā)給native層的Looper,Looper的wake方法核心源碼如下:

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

Looper的wake方法其實(shí)是使用write函數(shù)通過(guò)mWakeEventFd往管道寫入字符inc候引,其中TEMP_FAILURE_RETRY 是一個(gè)宏定義侯养, 當(dāng)執(zhí)行write方法失敗后,會(huì)不斷重復(fù)執(zhí)行澄干,直到執(zhí)行成功為止逛揩,在nativeinit中柠傍,我們已經(jīng)通過(guò)epoll_create方法監(jiān)聽(tīng)了mWakeEventFd的可讀事件,當(dāng)mWakeEventFd可讀時(shí)辩稽,epoll文件描述符就會(huì)監(jiān)聽(tīng)到惧笛,這時(shí)epoll_wait方法就會(huì)從管道中讀取事件返回,返回后就執(zhí)行消息處理邏輯逞泄,所以這里的往管道寫入字符inc患整,其實(shí)起到一個(gè)通知的作用,告訴監(jiān)聽(tīng)的線程有消息插入了消息隊(duì)列了喷众,快點(diǎn)醒過(guò)來(lái)(因?yàn)檫M(jìn)入了休眠狀態(tài))處理一下各谚。

我們跟著MessageQueue#nativeWake一路走下來(lái),小結(jié)一下:

  • 1到千、在java層插入消息到消息隊(duì)列后昌渤,就會(huì)根據(jù)需要判斷是否要調(diào)用nativeWake方法,如果調(diào)用憔四,就轉(zhuǎn)到2膀息。
  • 2、在nativeWake方法native實(shí)現(xiàn)中加矛,會(huì)把保存在java層的mPtr再轉(zhuǎn)換為NativeMessageQueue履婉,然后調(diào)用NativeMessageQueue的wake方法,最終調(diào)用Looper的wake方法斟览。
  • 3毁腿、前面講到Looper::pollInner方法是一個(gè)阻塞操作,當(dāng)管道中沒(méi)有事件時(shí)當(dāng)前線程就會(huì)進(jìn)入休眠等待苛茂,當(dāng)管道有事件就會(huì)立即返回已烤,從管道中讀取事件并處理,而Looper::wake方法就是一個(gè)喚醒操作妓羊,它就是通過(guò)前面創(chuàng)建的喚醒事件文件描述符mWakeEventFd來(lái)往管道中寫入內(nèi)容胯究,這時(shí)另外等待管道事件的線程就會(huì)被喚醒處理事件。

4躁绸、小結(jié)

1裕循、在創(chuàng)建java層的MessageQueue對(duì)象同時(shí)會(huì)在構(gòu)造中調(diào)用nativeInit方法創(chuàng)建native層的NativeMessageQueue,在創(chuàng)建NativeMessageQueue同時(shí)會(huì)在構(gòu)造中創(chuàng)建native層的Looper對(duì)象净刮,并把它保存到TLS區(qū)域中剥哑,然后返回NativeMessageQueue的指針給java層的mPtr保存;

2淹父、在創(chuàng)建Looper時(shí)會(huì)在構(gòu)造中通過(guò)管道與epoll機(jī)制建立一套native層的消息機(jī)制株婴,它首先創(chuàng)建一個(gè)喚醒文件描述符mWakeEventFd,然后使用epoll_create方法創(chuàng)建一個(gè)epoll文件描述符mEpollFd和管道暑认,然后使用epoll_ctl把mWakeEventFd添加到mEpollFd的監(jiān)控范圍內(nèi)困介;

3大审、當(dāng)java層使用Handler發(fā)送消息時(shí),會(huì)把消息插入到消息隊(duì)列中座哩,然后根據(jù)情況調(diào)用nativeWake方法喚醒阻塞線程徒扶,nativeWake方法會(huì)調(diào)用到native層的Looper的wake方法,里面會(huì)通過(guò)mWakeEventFd往管道中寫入一個(gè)字符八回,喚醒阻塞線程處理消息酷愧;

4、當(dāng)java層使用Looper的loop方法取消息時(shí)缠诅,如果沒(méi)有消息溶浴,調(diào)用nativePollOnce方法進(jìn)入阻塞狀態(tài),這時(shí)nativePollOnce方法會(huì)調(diào)用到native層的Looper的pollInner方法管引,里面會(huì)使用epoll_wait等待事件發(fā)生或超時(shí)士败,當(dāng)mEpollFd監(jiān)聽(tīng)的任何文件描述符(包括mWakeEventFd)的相應(yīng)IO事件發(fā)生時(shí),epoll_wait方法就會(huì)返回褥伴,返回就會(huì)通過(guò)native層的MessageHandler處理native層的Message谅将,處理完native層消息后,再返回處理java層的消息重慢。

總結(jié)

Java層和Native層的MessageQueue通過(guò)JNI建立關(guān)聯(lián)饥臂,從而使得MessageQueue成為Java層和Native層的樞紐,既能處理上層消息似踱,也能處理native層消息隅熙,而Handler/Looper/Message這三大類在Java層與Native層之間沒(méi)有任何的關(guān)聯(lián),只是分別在Java層和Native層的消息模型中具有相似的功能核芽,都是彼此獨(dú)立的囚戚,各自實(shí)現(xiàn)相應(yīng)的邏輯。

這里我們可以回答為什么java層的loop方法是死循環(huán)但卻不會(huì)消耗性能這個(gè)問(wèn)題:

因?yàn)閖ava層的消息機(jī)制是依賴native層的消息機(jī)制來(lái)實(shí)現(xiàn)的轧简,而native層的消息機(jī)制是通過(guò)Linux的管道和epoll機(jī)制實(shí)現(xiàn)的驰坊,epoll機(jī)制是一種高效的IO多路復(fù)用機(jī)制, 它使用一個(gè)文件描述符管理多個(gè)描述符哮独,java層通過(guò)mPtr指針也就共享了native層的epoll機(jī)制的高效性拳芙,當(dāng)loop方法中取不到消息時(shí),便阻塞在MessageQueue的next方法皮璧,而next方法阻塞在nativePollOnce方法舟扎,nativePollOnce方法通過(guò)JNI調(diào)用進(jìn)入到native層中去,最終nativePollOnce方法阻塞在epoll_wait方法中恶导,epoll_wait方法會(huì)讓當(dāng)前線程釋放CPU資源進(jìn)入休眠狀態(tài)浆竭,等到下一個(gè)消息到達(dá)(mWakeEventFd會(huì)往管道寫入字符)或監(jiān)聽(tīng)的其他事件發(fā)生時(shí)就會(huì)喚醒線程浸须,然后處理消息惨寿,所以就算loop方法是死循環(huán)邦泄,當(dāng)線程空閑時(shí),它會(huì)進(jìn)入休眠狀態(tài)裂垦,不會(huì)消耗大量的CPU資源顺囊。

以上就是本文的所有內(nèi)容,希望大家有所收獲蕉拢。

參考資料:

epoll特碳、looper.loop主線程阻塞

Android消息機(jī)制2-Handler

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市晕换,隨后出現(xiàn)的幾起案子午乓,更是在濱河造成了極大的恐慌,老刑警劉巖闸准,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件益愈,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡夷家,警方通過(guò)查閱死者的電腦和手機(jī)蒸其,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)库快,“玉大人摸袁,你說(shuō)我怎么就攤上這事∫迤粒” “怎么了靠汁?”我有些...
    開(kāi)封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)湿蛔。 經(jīng)常有香客問(wèn)我膀曾,道長(zhǎng),這世上最難降的妖魔是什么阳啥? 我笑而不...
    開(kāi)封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任添谊,我火速辦了婚禮,結(jié)果婚禮上察迟,老公的妹妹穿的比我還像新娘斩狱。我一直安慰自己,他們只是感情好扎瓶,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布所踊。 她就那樣靜靜地躺著,像睡著了一般概荷。 火紅的嫁衣襯著肌膚如雪秕岛。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音继薛,去河邊找鬼修壕。 笑死,一個(gè)胖子當(dāng)著我的面吹牛遏考,可吹牛的內(nèi)容都是我干的慈鸠。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼灌具,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼青团!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起咖楣,我...
    開(kāi)封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤督笆,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后诱贿,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體胖腾,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年瘪松,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了咸作。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡宵睦,死狀恐怖记罚,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情壳嚎,我是刑警寧澤桐智,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站烟馅,受9級(jí)特大地震影響说庭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜郑趁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一刊驴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧寡润,春花似錦捆憎、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至变抽,卻和暖如春础拨,著一層夾襖步出監(jiān)牢的瞬間氮块,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工诡宗, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留雇锡,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓僚焦,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親曙痘。 傳聞我的和親對(duì)象是個(gè)殘疾皇子芳悲,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容