1. 程式人生 > >[M0]Android Native層Looper詳解

[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, 根據MessageMessageHandler建立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()函式的具體實現:

  1. 首先根據uptime在mMessageEnvelopes遍歷,找到合適的位置,並將message 封裝成MessageEnvlope,插入找到的位置上。
  2. 然後決定是否要喚醒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的實現就介紹到這裡了。如有不清楚的地方,歡迎留言。