Android 消息機制底層原理

Handler與Looper、MessageQueue共同實現了線程間消息傳遞。MessageQueue的底層實現是利用管道和epoll機制來實現的。

概括:當我們查看Looper.loop()方法時,會發現其中有一個無線循環。那麼這其中的原因又是啥呢。當調用Looper.prepare()方法時,底層會創建一個管道,另外還會創建一個epoll實例去監聽管道的讀文件描述符。

當執行loop方法時,會調用epoll_wait去監聽epoll實例中所監聽的文件描述符有沒有對應的事件,如果沒有的話,該方法就會堵塞,所有for循環就沒有無限執行下去。當有其他線程向handler發送數據時,就會向管道寫數據,那麼epoll_wait方法堵塞就會喚醒。

然後我們把管道中的數據都讀出來。下一次進行for循壞的時候又會調用epoll_wait方法,如果消息隊列沒有消息時,就又會堵塞了。

一:什麼是管道

管道是一種把兩個進程之間的標準輸入和標準輸出連接起來的機制,從而提供一種讓多個進程間通信的方法。通俗一點說就是一個進程向管道寫入數據,另外一個進程可以從管道中讀取該數據。(ps:不只進程,線程當然也是可以的)

當進程創建管道時,每次都需要提供兩個文件描述符來操作管道。其中一個對管道進行寫操作,另一個對管道進行讀操作。

  1. 使用pipe函數創建一個管道
int pipefd[2];
pipe(pipefd);

當創建管道成功後,pipefd數組將會被賦值,其中pipefd[0]爲管道的讀端文件描述符,pipedf[1]爲管道的寫端文件描述符。

  1. 使用pipe函數創建是匿名管道,管道是沒有名字的,可以使用那倆個文件描述符對管道進行讀寫操作。

  2. 寫管道操作默認是堵塞的,也就是把數據全部寫入緩存後write函數才返回,如果緩存滿了就一直堵塞,直到緩存裏的數據被讀出,數據被全部寫入。

二. epoll機制

epoll機制:可以同時監聽多個文件描述符的IO讀寫事件而設計的。

1. 創建一個epoll實例

int epfd = epoll_create(intsize);  

參數 initsize 爲epoll實例需要監聽的IO讀寫事件的數目大小。epfd 爲epoll實例的文件描述符。

2. 使用epoll來監聽某個文件描述符上的事件

int epoll_ctl(int epfd, intop, int fd, struct epoll_event* event); 

第一個參數是 epoll_create() 的返回值,
第二個參數 op 表示動作,用三個宏來表示:
EPOLL_CTL_ADD: 註冊新的fd到epfd中;
EPOLL_CTL_MOD: 修改已經註冊的fd的監聽事件;
EPOLL_CTL_DEL: 從epfd中刪除一個fd;
第三個參數是需要監聽的fd,
第四個參數是告訴內核需要監聽什麼事件

舉例:利用epoll來監聽管道的讀文件描述符

int pipefd[2];
pipe(pipefd);  //創建管道

int epfd = epoll_create(intsize); //創建epoll 
struct epoll_event eventItem;
memset(& eventItem,0,sizeof(epoll_event)); // 給eventItem分配內存
eventItem.events = EPOLLIN; //EPOLLIN  表示對應的文件描述符上有可讀數據
eventItem.data.fd = pipefd[0];
result = epoll_ctl(epfd,EPOLL_CTL_ADD,pipefd[0],&eventItem);

分析: EventItem是我們定義的事件,事件的類型爲EPOLLIN。表示事件爲對應的文件描述符上有可讀數據,eventItem.data.fd 指定了特定文件描述符。調用epoll_ctl 去監聽管道讀文件描述符上是否有流到達

3. 使用epoll_wait 來監聽註冊在epoll實例中的文件描述符的IO讀寫事件

epoll實例可以註冊多個文件描述符,epoll_wait監聽epoll實例,這正是多路複用機制,一個epoll監聽了多個IO事件。

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
  • 參數events用來從內核得到事件的集合
  • maxevents告之內核這個events有多大(數組成員的個數),這個maxevents的值不能大於創建epoll_create()時的size
  • 參數timeout是超時時間(毫秒,0會立即返回,-1永久等待)
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
int eventCount = epoll_wait(mEpollFd,eventItems,EPOLL_MAX_EVENTS,timeoutMillis)

我們接着利用前面epoll_ctl 註冊管道的讀文件描述符。這裏我們對epoll實例進行監聽等待。當管道的讀文件描述符沒有數據時,這裏會被阻塞。當有其他線程或者進程向管道寫文件描述符寫入數據時,這時epoll_wait會將數據放入eventItems數組中,通過遍歷,我們可以拿到對應的事件。

作者:xinwa
鏈接:https://www.jianshu.com/p/7963755acf59

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章