[M0]Android Native層Looper詳解
前言
我們知道Java 層的Looper 的訊息佇列在沒有訊息處理的時候,會wait在MessageQueue.next() 函式裡,對於MessageQueue.next() 函式是如何實現的wait,卻是一知半解。而且Android Framework部分有很多在Native層使用Looper 監聽檔案描述符的用法,比如InputDispatcher等,瞭解Android Native 層Looper的實現,可以對整個Android系統的訊息迴圈機制有更深入的理解。
Looper介面說明
Function | Desription |
---|---|
Looper(bool allowNonCallbacks); | Looper 建構函式 |
static sp< Looper> prepare(int opts); | 如果該執行緒沒有繫結Looper,才建立Looper,否則直接返回。 |
int pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData); | 輪詢,等待事件發生 |
void wake(); | 喚醒Looper |
void sendMessage(const sp< MessageHandler>& handler, const Message& message); | 傳送訊息 |
int addFd(int fd, int ident, int events, Looper_callbackFunc callback, void* data); | 新增要監聽的檔案描述符fd |
Class Diagram
Looper 類圖如下:
在sendMessage()時,需要指定MessageHandler, 根據Message 和 MessageHandler建立MessageEnvelope 。然後將MessageEnvelope 新增到Looper的訊息佇列 mMessageEnvelopes 中。
Native Looper除了處理Message之外,還可以監聽指定的檔案描述符
- 通過addFd() 新增要監聽的fd到epoll的監聽佇列中,並將傳進來的fd,ident,callback,data 封裝成Request 物件,然後加入到Looper 的mRequests 中。
- 當該fd有事件發生時,epoll_wait()會返回epoll event,然後從mRequests中找到對應的request物件,並加上返回的epoll event 型別(EPOLLIN、EPOLLOUT…)封裝成Response物件,加入到mResponses 中。
- 然後在需要處理Responses的時候,從mResponses遍歷取出Response進行處理。
int addFd(int fd, int ident, int events, Looper_callbackFunc callback, void* data);
int addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data);
Create Looper
Looper提供兩種方式建立Looper:
直接呼叫Looper 建構函式:
Looper(bool allowNonCallbacks);
呼叫Looper的靜態函式 prepare() :如果執行緒已經有對應的Looper,則直接返回,否則才會建立新的Looper。
static sp<Looper> prepare(int opts);
Looper的建構函式裡主要做兩件事情:
- 呼叫eventfd(0, EFD_NONBLOCK)返回mWakeEventFd,用於喚醒epoll_wait()
- 呼叫rebuildEpollLocked() 建立epoll 檔案描述符,並將mWakeEventFd加入到epoll監聽佇列中
mEpollFd = epoll_create(EPOLL_SIZE_HINT);
memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
eventItem.events = EPOLLIN;
eventItem.data.fd = mWakeEventFd;
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
關於mWakeEventFd
關於eventfd也是linux新增的一個API,用於執行緒之間的通訊。之前版本中是通過pipe,得到read&write fd:來實現的wakeup looper功能。
當需要喚醒Looper時,呼叫wake(), 會往mWakeEventFd中write 1
void Looper::wake() {
...
uint64_t inc = 1;
ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
epoll_wait() 則會返回。然後呼叫awoken() ,
void Looper::awoken() {
...
uint64_t counter;
TEMP_FAILURE_RETRY(read(mWakeEventFd, &counter, sizeof(uint64_t)));
Send Message
傳送訊息是指將訊息插入到訊息佇列 mMessageEnvelopes。mMessageEnvelopes 裡面的是根據時間順序排列存放MessageEnvlope:下標越小,越早被處理。
傳送訊息的函式有如下三個,但最終都是呼叫sendMessageAtTime() 來實現的。
void sendMessage(const sp<MessageHandler>& handler, const Message& message);
void sendMessageDelayed(nsecs_t uptimeDelay, const sp<MessageHandler>& handler,
const Message& message);
void sendMessageAtTime(nsecs_t uptime, const sp<MessageHandler>& handler,
const Message& message);
來看一下sendMessageAtTime()函式的具體實現:
- 首先根據uptime在mMessageEnvelopes遍歷,找到合適的位置,並將message 封裝成MessageEnvlope,插入找到的位置上。
- 然後決定是否要喚醒Looper:
- 如果Looper此時正在派發message,則不需要wakeup Looper。因為這一次looper處理完訊息之後,會重新估算下一次epoll_wait() 的wakeup時間。
- 如果是插在訊息佇列的頭部,則需要立即wakeup Looper。
Poll Looper
要讓Looper執行起來才能處理訊息。
Looper提供了介面:pollOnce()
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData);
如果Looper中沒有任何要處理的event/message,則會阻塞在epoll_wait() 等待事件到來。
呼叫流程:pollOnce() ->pollInner()->epoll_wait()
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
epoll_wait()其有三種情況會返回,返回值eventCount為上來的epoll event數量。
- 出現錯誤返回, eventCount < 0;
- timeout返回,eventCount = 0,表明監聽的檔案描述符中都沒有事件發生,將直接進行native message的處理;
- 監聽的檔案描述符中有事件發生導致的返回,eventCount > 0; 有eventCount 數量的epoll event 上來。
Dispatch Message
Message/Event 都是在pollOnce() 和 pollInner() 中派發處理的。
pollOnce()
- 主要是 先處理mResponses中 還沒有被處理的事件:加入epoll監聽列表的fd,但是沒有指定callback,但是可以通過返回事件的ident來require caller進行處理的。
- 然後呼叫pollInner(), pollInner() 呼叫 epoll_wait() 阻塞等待。這裡會處理訊息佇列 mMessageEnvelopes 的Message,和 mResponses中ident 為POLL_CALLBACK的事件。
- pollInner() 中只處理了mResponses中ident 為POLL_CALLBACK的事件,其他ident >= 0的事件,則在這時處理,處理完返回ident,以便caller 知道是否需要繼續處理。
pollInner():
調整timeout:Adjust the timeout based on when the next message is due.
- mNextMessageUptime 是 訊息佇列 mMessageEnvelopes 中最近一個即將要被處理的message的時間點。
- 所以需要根據mNextMessageUptime 與 呼叫者傳下來的timeoutMillis 比較計算出一個最小的timeout,這將決定epoll_wait() 可能會阻塞多久才會返回。
epoll_wait()
- 處理epoll_wait() 返回的epoll events.
判斷epoll event 是哪個fd上發生的事件
- 如果是mWakeEventFd,則執行awoken(). awoken() 只是將資料read出來,然後繼續往下處理了。其目的也就是使epoll_wait() 從阻塞中返回。
- 如果是通過Looper.addFd() 介面加入到epoll監聽佇列的fd,並不是立馬處理,而是先push到mResponses,後面再處理。
- 處理訊息佇列 mMessageEnvelopes 中的Message.
- 如果還沒有到處理時間,就更新一下mNextMessageUptime
- 處理剛才放入mResponses中的 事件.
- 只處理ident 為POLL_CALLBACK的事件。其他事件在pollOnce中處理
小結
主要針對三部分的事件進行派發處理:
- native層通過 sendMessage() 存放在mMessageEnvelopes中的MessageEnvelope。
- 通過 addFd() 加入epoll監聽佇列的fd,並且其對應的callback不為null的。
- 剩下的就是加入epoll監聽列表的fd,但是沒有指定callback,但是可以通過返回事件的ident來require caller進行處理的。
他們被處理的順序如同以上描述順序。
總結
關於Android Native 層的Looper的實現就介紹到這裡了。如有不清楚的地方,歡迎留言。