亚洲精品久久久中文字幕-亚洲精品久久片久久-亚洲精品久久青草-亚洲精品久久婷婷爱久久婷婷-亚洲精品久久午夜香蕉

您的位置:首頁技術(shù)文章
文章詳情頁

Android nativePollOnce函數(shù)解析

瀏覽:2日期:2022-09-20 09:52:16

nativePollOnce的實(shí)現(xiàn)函數(shù)是android_os_MessageQueue_nativePollOnce,代碼如下:

android_os_MessageQueue.cpp

static void android_os_MessageQueue_nativePollOnce(JNIEnv*env, jobject obj, jintptr, jint timeoutMillis) NativeMessageQueue*nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr); //取出NativeMessageQueue對象,并調(diào)用它的pollOnce nativeMessageQueue->pollOnce(timeoutMillis);}//分析pollOnce函數(shù)void NativeMessageQueue::pollOnce(inttimeoutMillis) { mLooper->pollOnce(timeoutMillis); //重任傳遞到Looper的pollOnce函數(shù)}

Looper的pollOnce函數(shù)如下:

Looper.cpp

inline int pollOnce(int timeoutMillis) { return pollOnce(timeoutMillis, NULL, NULL, NULL);}

上面的函數(shù)將調(diào)用另外一個有4個參數(shù)的pollOnce函數(shù),這個函數(shù)的原型如下:int pollOnce(int timeoutMillis, int* outFd, int*outEvents, void** outData)其中:

timeOutMillis參數(shù)為超時(shí)等待時(shí)間。如果為-1,則表示無限等待,直到有事件發(fā)生為止。如果值為0,則無需等待立即返回。 outFd用來存儲發(fā)生事件的那個文件描述符。 outEvents用來存儲在該文件描述符[[1]上發(fā)生了哪些事件,目前支持可讀、可寫、錯誤和中斷4個事件。這4個事件其實(shí)是從epoll事件轉(zhuǎn)化而來。后面我們會介紹大名鼎鼎的epoll。 outData用于存儲上下文數(shù)據(jù),這個上下文數(shù)據(jù)是由用戶在添加監(jiān)聽句柄時(shí)傳遞的,它的作用和pthread_create函數(shù)最后一個參數(shù)param一樣,用來傳遞用戶自定義的數(shù)據(jù)。

另外,pollOnce函數(shù)的返回值也具有特殊的意義,具體如下:

當(dāng)返回值為ALOOPER_POLL_WAKE時(shí),表示這次返回是由wake函數(shù)觸發(fā)的,也就是管道寫端的那次寫事件觸發(fā)的。 返回值為ALOOPER_POLL_TIMEOUT表示等待超時(shí)。 返回值為ALOOPER_POLL_ERROR,表示等待過程中發(fā)生錯誤。

返回值為ALOOPER_POLL_CALLBACK,表示某個被監(jiān)聽的句柄因某種原因被觸發(fā)。這時(shí),outFd參數(shù)用于存儲發(fā)生事件的文件句柄,outEvents用于存儲所發(fā)生的事件。上面這些知識是和epoll息息相關(guān)的。提示查看Looper的代碼會發(fā)現(xiàn),Looper采用了編譯選項(xiàng)(即#if和#else)來控制是否使用epoll作為I/O復(fù)用的控制中樞。鑒于現(xiàn)在大多數(shù)系統(tǒng)都支持epoll,這里僅討論使用epoll的情況。

1.epoll基礎(chǔ)知識介紹

epoll機(jī)制提供了Linux平臺上最高效的I/O復(fù)用機(jī)制,因此有必要介紹一下它的基礎(chǔ)知識。從調(diào)用方法上看,epoll的用法和select/poll非常類似,其主要作用就是I/O復(fù)用,即在一個地方等待多個文件句柄的I/O事件。下面通過一個簡單例子來分析epoll的工作流程。

epoll工作流程分析案例

/*使用epoll前,需要先通過epoll_create函數(shù)創(chuàng)建一個epoll句柄。下面一行代碼中的10表示該epoll句柄初次創(chuàng)建時(shí)候分配能容納10個fd相關(guān)信息的緩存。對于2.6.8版本以后的內(nèi)核,該值沒有實(shí)際作用,這里可以忽略。其實(shí)這個值的主要目的是確定分配一塊多大的緩存。現(xiàn)在的內(nèi)核都支持動態(tài)拓展這塊緩存,所以該值就沒有意義了*/ int epollHandle = epoll_create(10);/* 得到epoll句柄后,下一步就是通過epoll_ctl把需要監(jiān)聽的文件句柄加入到epoll句柄中。 除了指定文件句柄本身的fd值外,同時(shí)還需要指定在該fd上等待什么事件。epoll支持四類事件, 分別是EPOLLIN(句柄可讀)、EPOLLOUT(句柄可寫),EPOLLERR(句柄錯誤)、EPOLLHUP(句柄斷)。 epoll定義了一個結(jié)構(gòu)體struct epoll_event來表達(dá)監(jiān)聽句柄的訴求。 假設(shè)現(xiàn)在有一個監(jiān)聽端的socket句柄listener,要把它加入到epoll句柄中。 */ structepoll_event listenEvent; //先定義一個event /* EPOLLIN表示可讀事件,EPOLLOUT表示可寫事件,另外還有EPOLLERR,EPOLLHUP表示 系統(tǒng)默認(rèn)會將EPOLLERR加入到事件集合中 */ listenEvent.events= EPOLLIN;//指定該句柄的可讀事件 //epoll_event中有一個聯(lián)合體叫data,用來存儲上下文數(shù)據(jù),本例的上下文數(shù)據(jù)就是句柄自己 listenEvent.data.fd= listenEvent;/* EPOLL_CTL_ADD將監(jiān)聽fd和監(jiān)聽事件加入到epoll句柄的等待隊(duì)列中; EPOLL_CTL_DEL將監(jiān)聽fd從epoll句柄中移除; EPOLL_CTL_MOD修改監(jiān)聽fd的監(jiān)聽事件,例如本來只等待可讀事件,現(xiàn)在需要同時(shí)等待可寫事件,那么修改listenEvent.events 為EPOLLIN|EPOLLOUT后,再傳給epoll句柄 */ epoll_ctl(epollHandle,EPOLL_CTL_ADD,listener,&listenEvent); /* 當(dāng)把所有感興趣的fd都加入到epoll句柄后,就可以開始坐等感興趣的事情發(fā)生了。 為了接收所發(fā)生的事情,先定義一個epoll_event數(shù)組 */struct epoll_eventresultEvents[10]; inttimeout = -1; while(1) { /* 調(diào)用epoll_wait用于等待事件,其中timeout可以指定一個超時(shí)時(shí)間, resultEvents用于接收發(fā)生的事件,10為該數(shù)組的大小。 epoll_wait函數(shù)的返回值有如下含義: nfds大于0表示所監(jiān)聽的句柄上有事件發(fā)生; nfds等于0表示等待超時(shí); nfds小于0表示等待過程中發(fā)生了錯誤 */ int nfds= epoll_wait(epollHandle, resultEvents, 10, timeout); if(nfds== -1) { // epoll_wait發(fā)生了錯誤 } elseif(nfds == 0) { //發(fā)生超時(shí),期間沒有發(fā)生任何事件 } else { //resultEvents用于返回那些發(fā)生了事件的信息 for(int i = 0; i < nfds; i++) { struct epoll_event & event =resultEvents[i]; if(event & EPOLLIN) { /* 收到可讀事件。到底是哪個文件句柄發(fā)生該事件呢?可通過event.data這個聯(lián)合體取得 之前傳遞給epoll的上下文數(shù)據(jù),該上下文信息可用于判斷到底是誰發(fā)生了事件。 */ } .......//其他處理 } }}

epoll整體使用流程如上面代碼所示,基本和select/poll類似,不過作為Linux平臺最高效的I/O復(fù)用機(jī)制,這里有些內(nèi)容供讀者參考,epoll的效率為什么會比select高?其中一個原因是調(diào)用方法。每次調(diào)用select時(shí),都需要把感興趣的事件復(fù)制到內(nèi)核中,而epoll只在epll_ctl進(jìn)行加入的時(shí)候復(fù)制一次。另外,epoll內(nèi)部用于保存事件的數(shù)據(jù)結(jié)構(gòu)使用的是紅黑樹,查找速度很快。而select采用數(shù)組保存信息,不但一次能等待的句柄個數(shù)有限,并且查找起來速度很慢。當(dāng)然,在只等待少量文件句柄時(shí),select和epoll效率相差不是很多,但筆者還是推薦使用epoll。epoll等待的事件有兩種觸發(fā)條件,一個是水平觸發(fā)(EPOLLLEVEL),另外一個是邊緣觸發(fā)(EPOLLET,ET為Edge Trigger之意),這兩種觸發(fā)條件的區(qū)別非常重要。讀者可通過man epoll查閱系統(tǒng)提供的更為詳細(xì)的epoll機(jī)制。最后,關(guān)于pipe,還想提出一個小問題供讀者思考討論:為什么Android中使用pipe作為線程間通訊的方式?對于pipe的寫端寫入的數(shù)據(jù),讀端都不感興趣,只是為了簡單的喚醒。POSIX不是也有線程間同步函數(shù)嗎?為什么要用pipe呢?關(guān)于這個問題的答案,可參見筆者一篇博文“隨筆之如何實(shí)現(xiàn)一個線程池”。

2. pollOnce函數(shù)分析

下面分析帶4個參數(shù)的pollOnce函數(shù),代碼如下:

Looper.cpp

int Looper::pollOnce(int timeoutMillis, int*outFd, int* outEvents,void** outData) { intresult = 0; for (;;){ //一個無限循環(huán) //mResponses是一個Vector,這里首先需要處理response while (mResponseIndex < mResponses.size()) { const Response& response = mResponses.itemAt(mResponseIndex++); ALooper_callbackFunc callback = response.request.callback; if (!callback) {//首先處理那些沒有callback的Responseint ident = response.request.ident; //ident是這個Response的idint fd = response.request.fd;int events = response.events;void* data = response.request.data;......if (outFd != NULL) *outFd = fd;if (outEvents != NULL) *outEvents = events;if (outData != NULL) *outData = data;//實(shí)際上,對于沒有callback的Response,pollOnce只是返回它的 //ident,并沒有實(shí)際做什么處理。因?yàn)闆]有callback,所以系統(tǒng)也不知道如何處理return ident; } } if(result != 0) { if (outFd != NULL) *outFd = 0; if (outEvents != NULL) *outEvents = NULL; if (outData != NULL) *outData = NULL; return result; } //調(diào)用pollInner函數(shù)。注意,它在for循環(huán)內(nèi)部 result = pollInner(timeoutMillis); }}

初看上面的代碼,可能會讓人有些丈二和尚摸不著頭腦。但是把pollInner函數(shù)分析完畢,大家就會明白很多。pollInner函數(shù)非常長,把用于調(diào)試和統(tǒng)計(jì)的代碼去掉,結(jié)果如下:

Looper.cpp

int Looper::pollInner(int timeoutMillis) { if(timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) { nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); ......//根據(jù)Native Message的信息計(jì)算此次需要等待的時(shí)間 timeoutMillis= messageTimeoutMillis; } intresult = ALOOPER_POLL_WAKE; mResponses.clear(); mResponseIndex = 0;#ifdef LOOPER_USES_EPOLL //我們只討論使用epoll進(jìn)行I/O復(fù)用的方式 structepoll_event eventItems[EPOLL_MAX_EVENTS]; //調(diào)用epoll_wait,等待感興趣的事件或超時(shí)發(fā)生 inteventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);#else ......//使用別的方式進(jìn)行I/O復(fù)用#endif //從epoll_wait返回,這時(shí)候一定發(fā)生了什么事情 mLock.lock(); if(eventCount < 0) { //返回值小于零,表示發(fā)生錯誤 if(errno == EINTR) { goto Done; } //設(shè)置result為ALLOPER_POLL_ERROR,并跳轉(zhuǎn)到Done result = ALOOPER_POLL_ERROR; gotoDone; } //eventCount為零,表示發(fā)生超時(shí),因此直接跳轉(zhuǎn)到Done if(eventCount == 0) { result = ALOOPER_POLL_TIMEOUT; gotoDone; }#ifdef LOOPER_USES_EPOLL //根據(jù)epoll的用法,此時(shí)的eventCount表示發(fā)生事件的個數(shù) for (inti = 0; i < eventCount; i++) { intfd = eventItems[i].data.fd; uint32_t epollEvents = eventItems[i].events; /* 之前通過pipe函數(shù)創(chuàng)建過兩個fd,這里根據(jù)fd知道是管道讀端有可讀事件。 讀者還記得對nativeWake函數(shù)的分析嗎?在那里我們向管道寫端寫了一個”W”字符,這樣 就能觸發(fā)管道讀端從epoll_wait函數(shù)返回了 */ if(fd == mWakeReadPipeFd) { if (epollEvents & EPOLLIN) {//awoken函數(shù)直接讀取并清空管道數(shù)據(jù),讀者可自行研究該函數(shù)awoken(); } ...... }else { /* mRequests和前面的mResponse相對應(yīng),它也是一個KeyedVector,其中存儲了 fd和對應(yīng)的Request結(jié)構(gòu)體,該結(jié)構(gòu)體封裝了和監(jiān)控文件句柄相關(guān)的一些上下文信息, 例如回調(diào)函數(shù)等。我們在后面的小節(jié)會再次介紹該結(jié)構(gòu)體 */ ssize_t requestIndex = mRequests.indexOfKey(fd); if (requestIndex >= 0) {int events = 0;//將epoll返回的事件轉(zhuǎn)換成上層LOOPER使用的事件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;//每處理一個Request,就相應(yīng)構(gòu)造一個ResponsepushResponse(events, mRequests.valueAt(requestIndex)); } ...... } }Done: ;#else ......#endif //除了處理Request外,還處理Native的Message mNextMessageUptime = LLONG_MAX; while(mMessageEnvelopes.size() != 0) { nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); const MessageEnvelope& messageEnvelope =mMessageEnvelopes.itemAt(0); if(messageEnvelope.uptime <= now) { {sp<MessageHandler> handler = messageEnvelope.handler;Message message = messageEnvelope.message;mMessageEnvelopes.removeAt(0);mSendingMessage = true;mLock.unlock();//調(diào)用Native的handler處理Native的Message//從這里也可看出NativeMessage和Java層的Message沒有什么關(guān)系handler->handleMessage(message); } mLock.lock(); mSendingMessage = false; result = ALOOPER_POLL_CALLBACK; }else { mNextMessageUptime = messageEnvelope.uptime; break; } } mLock.unlock(); //處理那些帶回調(diào)函數(shù)的Response for(size_t i = 0; i < mResponses.size(); i++) { const Response& response = mResponses.itemAt(i); ALooper_callbackFunc callback = response.request.callback; if(callback) {//有了回調(diào)函數(shù),就能知道如何處理所發(fā)生的事情了 int fd = response.request.fd; int events = response.events; void* data = response.request.data; //調(diào)用回調(diào)函數(shù)處理所發(fā)生的事件 int callbackResult = callback(fd, events, data); if (callbackResult == 0) {//callback函數(shù)的返回值很重要,如果為0,表明不需要再次監(jiān)視該文件句柄removeFd(fd); } result = ALOOPER_POLL_CALLBACK; } } returnresult;}

看完代碼了,是否還有點(diǎn)模糊?那么,回顧一下pollInner函數(shù)的幾個關(guān)鍵點(diǎn):

首先需要計(jì)算一下真正需要等待的時(shí)間。 調(diào)用epoll_wait函數(shù)等待。 epoll_wait函數(shù)返回,這時(shí)候可能有三種情況:

發(fā)生錯誤,則跳轉(zhuǎn)到Done處。超時(shí),這時(shí)候也跳轉(zhuǎn)到Done處。epoll_wait監(jiān)測到某些文件句柄上有事件發(fā)生。

假設(shè)epoll_wait因?yàn)槲募浔惺录祷兀藭r(shí)需要根據(jù)文件句柄來分別處理:

如果是管道讀這一端有事情,則認(rèn)為是控制命令,可以直接讀取管道中的數(shù)據(jù)。如果是其他FD發(fā)生事件,則根據(jù)Request構(gòu)造Response,并push到Response數(shù)組中。

真正開始處理事件是在有Done標(biāo)志的位置。

首先處理Native的Message。調(diào)用Native Handler的handleMessage處理該Message。處理Response數(shù)組中那些帶有callback的事件。上面的處理流程還是比較清晰的,但還是有個一個攔路虎,那就是mRequests,下面就來清剿這個攔路虎。

3.添加監(jiān)控請求

添加監(jiān)控請求其實(shí)就是調(diào)用epoll_ctl增加文件句柄。下面通過從Native的Activity找到的一個例子來分析mRequests。

android_app_NativeActivity.cpp

static jintloadNativeCode_native(JNIEnv* env, jobject clazz,jstring path, jstring funcName,jobject messageQueue, jstring internalDataDir, jstring obbDir, jstring externalDataDir, int sdkVersion, jobject jAssetMgr, jbyteArraysavedState){ ...... /* 調(diào)用Looper的addFd函數(shù)。第一個參數(shù)表示監(jiān)聽的fd;第二個參數(shù)0表示ident; 第三個參數(shù)表示需要監(jiān)聽的事件,這里為只監(jiān)聽可讀事件;第四個參數(shù)為回調(diào)函數(shù),當(dāng)該fd發(fā)生 指定事件時(shí),looper將回調(diào)該函數(shù);第五個參數(shù)code為回調(diào)函數(shù)的參數(shù)*/ code->looper->addFd(code->mainWorkRead,0, ALOOPER_EVENT_INPUT,mainWorkCallback, code); ......}

Looper的addFd代碼如下所示:

Looper.cpp

int Looper::addFd(int fd, int ident, int events, ALooper_callbackFunccallback, void* data) { if (!callback) { //判斷該Looper是否支持不帶回調(diào)函數(shù)的文件句柄添加。一般不支持,因?yàn)闆]有回調(diào)函數(shù) //Looper也不知道如何處理該文件句柄上發(fā)生的事情 if(! mAllowNonCallbacks) { return -1; } ...... } #ifdefLOOPER_USES_EPOLL intepollEvents = 0; //將用戶的事件轉(zhuǎn)換成epoll使用的值 if(events & ALOOPER_EVENT_INPUT) epollEvents |= EPOLLIN; if(events & ALOOPER_EVENT_OUTPUT) epollEvents |= EPOLLOUT; { AutoMutex _l(mLock); Request request; //創(chuàng)建一個Request對象 request.fd = fd; //保存fd request.ident = ident; //保存id request.callback = callback; //保存callback request.data = data; //保存用戶自定義數(shù)據(jù) struct epoll_event eventItem; memset(& eventItem, 0, sizeof(epoll_event)); eventItem.events = epollEvents; eventItem.data.fd = fd; //判斷該Request是否已經(jīng)存在,mRequests以fd作為key值 ssize_t requestIndex = mRequests.indexOfKey(fd); if(requestIndex < 0) { //如果是新的文件句柄,則需要為epoll增加該fd int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem); ...... //保存Request到mRequests鍵值數(shù)組 mRequests.add(fd, request); }else { //如果之前加過,那么就修改該監(jiān)聽句柄的一些信息 int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_MOD, fd, &eventItem); ...... mRequests.replaceValueAt(requestIndex, request); } }#else ......#endif return1;}4.處理監(jiān)控請求

我們發(fā)現(xiàn)在pollInner函數(shù)中,當(dāng)某個監(jiān)控fd上發(fā)生事件后,就會把對應(yīng)的Request取出來調(diào)用。pushResponse(events, mRequests.itemAt(i));此函數(shù)如下:

Looper.cpp

void Looper::pushResponse(int events, constRequest& request) { Responseresponse; response.events = events; response.request = request; //其實(shí)很簡單,就是保存所發(fā)生的事情和對應(yīng)的Request mResponses.push(response); //然后保存到mResponse數(shù)組}

根據(jù)前面的知識可知,并不是單獨(dú)處理Request,而是需要先收集Request,等到Native Message消息處理完之后再做處理。這表明,在處理邏輯上,Native Message的優(yōu)先級高于監(jiān)控FD的優(yōu)先級。下面我們來了解如何添加Native的Message。

5. Native的sendMessage

Android 2.2中只有Java層才可以通過sendMessage往MessageQueue中添加消息,從4.0開始,Native層也支持sendMessage了[2]。sendMessage的代碼如下:

Looper.cpp

void Looper::sendMessage(constsp<MessageHandler>& handler, constMessage& message) { //Native的sendMessage函數(shù)必須同時(shí)傳遞一個Handler nsecs_tnow = systemTime(SYSTEM_TIME_MONOTONIC); sendMessageAtTime(now, handler, message); //調(diào)用sendMessageAtTime}void Looper::sendMessageAtTime(nsecs_t uptime, const sp<MessageHandler>& handler, const Message& message) { size_t i= 0; { //acquire lock AutoMutex _l(mLock); size_t messageCount = mMessageEnvelopes.size(); //按時(shí)間排序,將消息插入到正確的位置上 while (i < messageCount &&uptime >= mMessageEnvelopes.itemAt(i).uptime) { i += 1; }MessageEnvelope messageEnvelope(uptime, handler, message); mMessageEnvelopes.insertAt(messageEnvelope, i, 1); //mSendingMessage和Java層中的那個mBlocked一樣,是一個小小的優(yōu)化措施 if(mSendingMessage) { return; } } //喚醒epoll_wait,讓它處理消息 if (i ==0) { wake(); }}

1.注意,以后文件描述符也會簡寫為文件句柄。 ↩︎

2.我們這里略過了Android2.2到Android 4.0之間幾個版本中的代碼變化。 ↩︎

以上就是Android nativePollOnce函數(shù)解析的詳細(xì)內(nèi)容,更多關(guān)于Android nativePollOnce函數(shù)的資料請關(guān)注好吧啦網(wǎng)其它相關(guān)文章!

標(biāo)簽: Android
相關(guān)文章:
主站蜘蛛池模板: 日韩欧美一区二区三区不卡在线 | 99久久精品国产高清一区二区 | 欧美日韩午夜精品不卡综合 | 永久在线免费观看 | 国内自拍videos hd | 日韩1024| 国产成人啪午夜精品网站 | 莫菁在线 | 美国人与性xxxxxxx | 性做久久久久久网站 | chinese国产hdsex水滴 | 日韩视频免费 | 国产亚洲欧美日韩综合综合二区 | 另类综合视频 | 成年人黄色片视频 | 国产一区视频在线 | 国产第一页久久亚洲欧美国产 | 中文字幕在线观看不卡 | 免费国外性视频网站 | 91最新入口| 免费一级毛片在线播放 | 青青草青青操 | 国产图片区 | 日本特一级毛片免费视频 | 久青草免费视频手机在线观看 | 国内成人啪啪网站 | 看黄在线观看 | 成人伊人青草久久综合网破解版 | 久草网视频在线 | 久久国产精品亚洲一区二区 | 日本高清不卡二区 | 一级a做爰片欧欧美毛片4 | 97视频免费公开成人福利 | 国产日产亚洲欧美综合另类 | 亚洲视频高清 | 免费黄视频网站 | 欧美一区二区三区久久久人妖 | 国产欧美精品国产国产专区 | 美女三级毛片 | 欧美日韩一区二区在线视频 | 美女天堂网 |