前言
- 上一篇文章:Android消息機(jī)制java層
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ì)取出信封中的MessageHandler和Message,把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)容,希望大家有所收獲蕉拢。
參考資料: