Android Looper類程式碼分析
本文將分析一下Looper類的實現及其應用,程式碼位於
frameworks/native/lib/utils/Looper.cpp。主要分為如下幾個部分:
1. epoll系統呼叫介面簡介
2. Looper類程式碼分析
3. Looper類應用例項分析
一、epoll系統呼叫介面簡介
Looper事件機制實際上是依賴系統呼叫epoll實現的。它是一種I/O複用模型,即可以同時監控多個I/O事件。對於Looper來說,所謂的I/O事件就是所監控的檔案描述符上沒有有資料到達。epoll的主要介面如下所示 :
1. epoll_create():建立一個epoll例項,返回引用該例項的檔案描述符。
原型如下所示 :
int epoll_create(int size ); |
引數size指定了我們想通過epoll例項監控檔案描述符的數量。
2. epoll_ctl():操作與該epoll例項相關聯的興趣列表:新增一個檔案描述符到興趣列表中,從興趣列表中刪除一個現存的檔案描述符以及修改事件掩碼以決定要監控檔案描述符上發生的哪個事件。
原型如下所示:
int epoll_ctl(int epfd , int op , int fd , struct epoll_event * ev ); |
其中引數op可以取如下一些值:
EPOLL_CTL_ADD |
將fd加入了監控列表 |
EPOLL_CTL_MOD |
修改當前監控的fd相關資訊 |
EPOLL_CTL_DEL |
將fd從監控列表中刪除 |
3. epoll_wait():從I/O Ready列表中返回與epoll例項相關聯的項,即返回有事件發生的檔案描述符的數量。
原型如下所示:
int epoll_wait(int epfd , struct epoll_event * evlist , int maxevents , int timeout ); |
其中timeout值為-1時,表示無限等待直到有事件發生。為0時,執行一個非阻塞檢查後立即返回。大於0時,表示一個超時時間值。
另外,struct epoll_event
struct epoll_event {
uint32_t events; /* epoll events (bit mask) */
epoll_data_t data; /* User data */
};
主要的事件掩碼有:
EPOLLIN:代表有資料可讀
EPOLLOUT:代表有資料可寫
epoll_data_t的資料結構定義如下:
typedef union epoll_data {
void *ptr; /* Pointer to user-defined data */
int fd; /*File descriptor */
uint32_t u32; /* 32-bit integer */
uint64_t u64; /* 64-bit integer */
} epoll_data_t;
使用例項:
int epfd;
struct epoll_event ev;
epfd = epoll_create(5);
if (epfd == -1)
errExit("epoll_create");
ev.data.fd = fd;
ev.events = EPOLLIN;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, ev) == -1)
errExit("epoll_ctl");
...
epoll_wait(...);
二、Looper類程式碼分析
Looper類定義了一種事件介面,這裡所謂的事件就是檔案描述符上的I/O資料是否可讀或可寫。它提供了一系列介面來支援事件通知和響應,通過輪詢,利用epoll系統呼叫,可以偵測到發生在檔案描述符上的I/O事件。
在分析Looper類之前,我們先來看兩個與之相關的介面:
1. Looper訊息處理介面。
class MessageHandler : public virtual RefBase {
protected:
virtual ~MessageHandler() { }
public:
/**
* Handles a message.
*/
virtual void handleMessage(const Message& message) = 0;
};
與之相關的Looper類的幾個成員函式定義如下:
/**
* Enqueues a message to be processed by the specified handler.
*/
void sendMessage(const sp<MessageHandler>& handler, const Message& message);
/**
* Enqueues a message to be processed by the specified handler after all pending messages
* after the specified delay.
*/
void sendMessageDelayed(nsecs_t uptimeDelay, const sp<MessageHandler>& handler,
const Message& message);
/**
* Enqueues a message to be processed by the specified handler after all pending messages
* at the specified time.
*/
void sendMessageAtTime(nsecs_t uptime, const sp<MessageHandler>& handler,
const Message& message);
/**
* Removes all messages for the specified handler from the queue.
*/
void removeMessages(const sp<MessageHandler>& handler);
/**
* Removes all messages of a particular type for the specified handler from the queue.
*/
void removeMessages(const sp<MessageHandler>& handler, int what);
從上述成員函式的定義可以看到,Looper對MessageHandler都擁有強引用,所以需要通過顯式呼叫remoeveMessage將其刪掉。
此外,也定義了一個WeakMessageHandler類,它通過一個弱引用來引用一個MessageHandler物件,在需要的時候強化為強引用。
1. Looper回撥函式介面。
回撥函式類定義如下:
/**
* A looper callback.
*/
class LooperCallback : public virtual RefBase {
protected:
virtual ~LooperCallback() { }
public:
/**
* Handles a poll event for the given file descriptor.
* It is given the file descriptor it is associated with,
* a bitmask of the poll events that were triggered (typically ALOOPER_EVENT_INPUT),
* and the data pointer that was originally supplied.
*
* Implementations should return 1 to continue receiving callbacks, or 0
* to have this file descriptor and callback unregistered from the looper.
*/
virtual int handleEvent(int fd, int events, void* data) = 0;
};
同樣地,也定義了一個輔助類SimpleLooperCallback,它支援接受一個回撥函式指標。
typedef int (*ALooper_callbackFunc)(int fd, int events, void* data);
與之相關的Looper類的成員函式如下所示 :
int addFd(int fd, int ident, int events, ALooper_callbackFunc callback, void* data); int addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data); |
這兩個成員函式的主要作用是:將要監控的fd加入到Looper的事件監控列表中。這裡,可以指定回撥函式。當有事件發生時,Looper例項會自動呼叫回撥函式。如果回撥函式為空,則由呼叫者處理髮生的事件。
下面將分析Looper類的實現。
先分析下成員變數的意義:
const bool mAllowNonCallbacks; // immutable
int mWakeReadPipeFd; // immutable
int mWakeWritePipeFd; // immutable
Mutex mLock;
Vector<MessageEnvelope> mMessageEnvelopes; // guarded by mLock
bool mSendingMessage; // guarded by mLock
int mEpollFd; // immutable
// Locked list of file descriptor monitoring requests.
KeyedVector<int, Request> mRequests; // guarded by mLock
// This state is only used privately by pollOnce and does not require a lock since
// it runs on a single thread.
Vector<Response> mResponses;
size_t mResponseIndex;
nsecs_t mNextMessageUptime; // set to LLONG_MAX when none
它們的表示的意義如下所示:
mAllowNonCallbacks: 表示是否允許將檔案描述符加入監控物件時,指定回撥函式為空。
mWakeReadPipeFd:Looper類預設構造的雙向管道的只讀端。
mWakeWritePipeFd:Looper類預設構造的雙向管道的只寫端。
mLock:互斥訪問保護鎖,主要Looper類的一些成員變數的併發訪問。
mMessageEnvelopes:Looper例項包含的“訊息信封”集合。訊息信封主要包含如下屬性:
時間戳,訊息處理函式指標以及訊息本身。
mSendingMessage:當前Looper例項是否正在傳送訊息。
mEpollFd:epoll例項對應的描述符。
mRequests:當前Looper例項中的檔案描述符監控請求。對就的資料結構struct Request定義如下:
struct Request {
int fd;
int ident;
sp<LooperCallback> callback;
void* data;
};
其中,fd表示監控的檔案描述符,ident表示表示監控的事件標識。callback是事件發生時,對應的回撥函式。data為傳遞給回撥函式的自定義資料。
mResponses:當前的響應集合。資料結構Response的定義如下:
struct Response {
int events;
Request request;
};
mResponseIndex:響應索引號。
mNextMessageUptime:下一個訊息處理的時間。
接下來,看建構函式宣告:
Looper(bool allowNonCallbacks); |
引數allowNonCallbacks表示是否允許將檔案描述符加入監控物件時,指定回撥函式為空。
其實現如下所示:
首先,它建立了一個雙向管道,一端讀,一端寫。並將其設定為非阻塞模式。然後建立epoll例項,將只讀端管道檔案描述符中入到epoll的監控列表中,這樣保護epoll例項中至少包含有一個檔案描述符在其事件監控列表中。詳細程式碼如下所示 :
Looper::Looper(bool allowNonCallbacks) :
mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
int wakeFds[2];
int result = pipe(wakeFds);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe. errno=%d", errno);
mWakeReadPipeFd = wakeFds[0];
mWakeWritePipeFd = wakeFds[1];
result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking. errno=%d",
errno);
result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking. errno=%d",
errno);
// Allocate the epoll instance and register the wake pipe.
mEpollFd = epoll_create(EPOLL_SIZE_HINT);
LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance. errno=%d", errno);
struct epoll_event eventItem;
memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
eventItem.events = EPOLLIN;
eventItem.data.fd = mWakeReadPipeFd;
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance. errno=%d",
errno);
}
再來看與執行緒相關的幾個類的靜態函式:
static sp<Looper> prepare(int opts);
將一個Looper例項與呼叫者所在的執行緒關聯。Opts的值為:
ALOOPER_PREPARE_ALLOW_NON_CALLBACKS或0,它返回該Looper例項。
static void setForThread(const sp<Looper>& looper);
設定looper物件與當前執行緒關聯。如果當前looper物件已經存在,則替換掉。如果looper為NULL,則刪除當前關聯的looper物件。
static sp<Looper> getForThread();
返回當前執行緒關聯的Looper例項。
接下來看下兩個比較重要的成員函式:
1. int Looper::addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data)
該函式主要是將fd加入到Looper的事件監控列表中。如果allowNonCallbacks為false,則必須指定回撥函式,且此時ident值為ALOOPER_POLL_CALLBACK(-2),忽略傳入的indent的值,而回調函式為空時,傳入的ident值不能小於0 。實際上會通過系統呼叫epoll_ctl將fd加入到epoll例項的事件監控列表中。同時,也記錄下此次的監控資訊,封裝成一個Request例項,加入到成員變數mRequests當中。如果fd已經存在,則替換掉舊的Request物件。
2. void Looper::sendMessageAtTime(nsecs_t uptime, const sp<MessageHandler>& handler, const Message& message)
該函式主要作用就是傳送一個Message物件,實現就是註冊一個MessageEnvelop(訊息信封)例項,加入到成員變數mMessageEnvelopes,它是按訊息觸發的時間排序的。
最後,我們來看下它的核心成員函式pollOnce,基本流程圖如下所示 :
下面來分析上述過程:
1. Handle response
for (;;) {
while (mResponseIndex < mResponses.size()) {
const Response& response = mResponses.itemAt(mResponseIndex++);
int ident = response.request.ident;
if (ident >= 0) {
int fd = response.request.fd;
int events = response.events;
void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ pollOnce - returning signalled identifier %d: "
"fd=%d, events=0x%x, data=%p",
this, ident, fd, events, data);
#endif
if (outFd != NULL) *outFd = fd;
if (outEvents != NULL) *outEvents = events;
if (outData != NULL) *outData = data;
return ident;
}
}
針對回撥函式為空的情況,ident值必為一個大於等於0的值(注:有回撥函式時,indent的值為-2)。所以上述這段程式碼只會發生在回撥函式為空的情況,此時將返回發生事件的描述符,發生的事件以及返回的資料,供呼叫者進一步處理。
2. Handle result.
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;
}
...
}
這段程式碼實際上是根據pollInner的結果進行處理,實際上是針對設定了回撥函式的情況,因為設定了回撥函式,所以已經對發生的事件做了處理了,所以,不需要將發生事件的相關資訊再返回給呼叫者了。
3. pollInner
for(;;) {
...
result = pollInner(timeoutMillis);
}
3.1 Ajust the time out.
int Looper::pollInner(int timeoutMillis) {
...
// 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;
}
...
}
...
}
為什麼要調整超時時間值,原因很簡單:因為對於訊息來說,可能有多個訊息,且每個訊息觸發的時間點不同,一次事件的觸發導致epoll_wait返回並不能處理完所有的訊息,所有會多次呼叫epoll_wait函式,由於超時值是第一次呼叫時指定的,所以再次呼叫時,需要重新計算,要去掉已經消耗的時間。程式碼中now記錄當前的時間值,toMillisecondTimeoutDelya(...)計算這本次迴圈的超時值。上述的判斷條件指明瞭什麼情況下需要做些調整:
1. 當前的訊息觸發時間不早於當前時間。(即訊息沒有過時)
2. 上輪epoll_wait指定的超時值為-1或一個較大的數值(> messageTimeoutMillis)。
3.2 wait for event(epoll wait)
...
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
...
主要通過epoll_wait系統呼叫檢測事件的發生。
3.3 handle the event
...
for (int i = 0; i < eventCount; i++) {
int fd = eventItems[i].data.fd;
uint32_t epollEvents = eventItems[i].events;
if (fd == mWakeReadPipeFd) {
if (epollEvents & EPOLLIN) {
awoken();
} else {
ALOGW("Ignoring unexpected epoll events 0x%x on wake read pipe.", epollEvents);
}
} else {
ssize_t requestIndex = mRequests.indexOfKey(fd);
if (requestIndex >= 0) {
int events = 0;
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;
pushResponse(events, mRequests.valueAt(requestIndex));
} else {
ALOGW("Ignoring unexpected epoll events 0x%x on fd %d that is "
"no longer registered.", epollEvents, fd);
}
}
}
...
對於Looper物件內建的管道,處理EPOLLIN事件,而對於其他監聽的檔案描述符,則分別記錄下EPOLLIN, EPOLLOUT, EPOLLERR, EPOLLHUP並打包成Response物件加入到mResponses中進行處理。
3.4 invoke pending message callbacks
// Invoke pending message callbacks.
mNextMessageUptime = LLONG_MAX;
while (mMessageEnvelopes.size() != 0) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);
if (messageEnvelope.uptime <= now) {
// Remove the envelope from the list.
// We keep a strong reference to the handler until the call to handleMessage
// finishes. Then we drop it so that the handler can be deleted *before*
// we reacquire our lock.
{ // obtain handler
sp<MessageHandler> handler = messageEnvelope.handler;
Message message = messageEnvelope.message;
mMessageEnvelopes.removeAt(0);
mSendingMessage = true;
mLock.unlock();
#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
ALOGD("%p ~ pollOnce - sending message: handler=%p, what=%d",
this, handler.get(), message.what);
#endif
handler->handleMessage(message);
} // release handler
mLock.lock();
mSendingMessage = false;
result = ALOOPER_POLL_CALLBACK;
} else {
// The last message left at the head of the queue determines the next wakeup time.
mNextMessageUptime = messageEnvelope.uptime;
break;
}
}
messageEnvelope.uptime代表該訊息被處理的時機,先處理掉已經過時的訊息,即messageEnvelope.uptime <= now, 如果還有未過時的訊息,則記錄下它應該被處理的時間:mNextMessageUptime = messageEnvelope.uptime;也即下次被觸發的時間。這個值也作為3.1中調整epoll_wait超時時間的值。
3.5 invoke all response callback
對於回撥函式不為空的情形,在事件觸發後,就會自動執行呼叫者提供的回撥函式,如下面程式碼所示:
// Invoke all response callbacks.
for (size_t i = 0; i < mResponses.size(); i++) {
Response& response = mResponses.editItemAt(i);
if (response.request.ident == ALOOPER_POLL_CALLBACK) {
int fd = response.request.fd;
int events = response.events;
void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
ALOGD("%p ~ pollOnce - invoking fd event callback %p: fd=%d, events=0x%x, data=%p",
this, response.request.callback.get(), fd, events, data);
#endif
int callbackResult = response.request.callback->handleEvent(fd, events, data);
if (callbackResult == 0) {
removeFd(fd);
}
// Clear the callback reference in the response structure promptly because we
// will not clear the response vector itself until the next poll.
response.request.callback.clear();
result = ALOOPER_POLL_CALLBACK;
}
三、Looper類應用例項分析
下面來看下Looper類的API的使用。
1. Looper物件初始化
sp<Looper> mLooper = new Looper(true); ... mLooper.clear(); |
2. pollOnece函式的使用
StopWatch stopWatch("pollOnce"); int result = mLooper->pollOnce(1000); int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); |
返回值為result = ALOOPER_POLL_WAKE
3. 設定CallBack
定義回撥函式:
class CallbackHandler {
public:
void setCallback(const sp<Looper>& looper, int fd, int events) {
looper->addFd(fd, 0, events, staticHandler, this);
}
protected:
virtual ~CallbackHandler() { }
virtual int handler(int fd, int events) = 0;
private:
static int staticHandler(int fd, int events, void* data) {
return static_cast<CallbackHandler*>(data)->handler(fd, events);
}
};
class StubCallbackHandler : public CallbackHandler {
public:
int nextResult;
int callbackCount;
int fd;
int events;
StubCallbackHandler(int nextResult) : nextResult(nextResult),
callbackCount(0), fd(-1), events(-1) {
}
protected:
virtual int handler(int fd, int events) {
callbackCount += 1;
this->fd = fd;
this->events = events;
return nextResult;
}
};
使用例項:
Pipe pipe;
StubCallbackHandler handler(true);
pipe.writeSignal();
handler.setCallback(mLooper, pipe.receiveFd, ALOOPER_EVENT_INPUT);
StopWatch stopWatch("pollOnce");
int result = mLooper->pollOnce(100);
int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
...
result的值為ALOOPER_POLL_CALLBACK。
4. Callback為空的情形
若設定Callback為空,此時事件的識別符號ident必須是一個大於或等於0的值。如下程式碼所示:
const int expectedIdent = 5;
void* expectedData = this;
Pipe pipe;
pipe.writeSignal();
mLooper->addFd(pipe.receiveFd, expectedIdent, ALOOPER_EVENT_INPUT, NULL, expectedData);
StopWatch stopWatch("pollOnce");
int fd;
int events;
void* data;
int result = mLooper->pollOnce(100, &fd, &events, &data);
int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
此時返回值result等於ident的值。
5. 通過Looper傳送訊息
此種情況下一般不需要呼叫addFd,通過Looper預設建立的管道來監聽事件就行了。它的使用示例如下:
首先要定義一個MessageHandler的派生類,用於處理訊息:
class StubMessageHandler : public MessageHandler {
public:
Vector<Message> messages;
virtual void handleMessage(const Message& message) {
messages.push(message);
}
};
接著就可以通過SendMessage相關的函式傳送訊息到Looper例項上:
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
sp<StubMessageHandler> handler = new StubMessageHandler();
mLooper->sendMessageAtTime(now + ms2ns(100), handler, Message(MSG_TEST1));
StopWatch stopWatch("pollOnce");
int result = mLooper->pollOnce(1000);
int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
...
result = mLooper->pollOnce(1000);
elapsedMillis = ns2ms(stopWatch.elapsedTime());
...
result = mLooper->pollOnce(100);
elapsedMillis = ns2ms(stopWatch.elapsedTime());
第一次
elapsedMillis = 0;
result = ALOOPER_POLL_WAKE
Message size = 0;
第二次
elapsedMillis = 100
result = ALOOPER_POLL_CALLBACK
Message size = 1
第三次
result = ALOOPER_POLL_TIMEOUT
沒有訊息需要處理。